├── template
├── cpp
│ ├── algm.h
│ ├── ListNode.h
│ ├── TreeNode.h
│ └── parse_test.cpp
├── golang
│ ├── go.mod
│ ├── go.sum
│ ├── main_test.go
│ └── algm.go
└── java
│ ├── ListNode.java
│ └── TreeNode.java
├── images
├── copy.png
├── memo.gif
├── test.png
├── build.png
├── debug.gif
├── debug.png
├── history.png
├── search.gif
├── algorithm.png
├── shortcut.png
└── todayRecord.gif
├── .gitignore
├── .prettierrc.json
├── .vscodeignore
├── .vscode
├── extensions.json
├── settings.json
├── tasks.json
└── launch.json
├── media
├── dark
│ └── copy.svg
├── light
│ └── copy.svg
├── buildcode.js
├── ac.svg
├── algorithm.svg
├── history.css
├── highlight.css
└── markdown.css
├── src
├── model
│ ├── memo.ts
│ ├── command.ts
│ ├── question.cn.ts
│ ├── common.ts
│ └── question.ts
├── fileStorage
│ ├── README.md
│ ├── util.ts
│ ├── crc32_table.ts
│ └── db.ts
├── test
│ ├── suite
│ │ ├── extension.test.ts
│ │ ├── index.ts
│ │ └── util.test.ts
│ ├── runTest.ts
│ └── unit
│ │ ├── test.ts
│ │ └── fileStorage.test.ts
├── db.ts
├── common
│ ├── website.ts
│ ├── lang.ts
│ ├── transformCode.ts
│ ├── langConfig.ts
│ └── parseContent.ts
├── process.ts
├── provider
│ ├── completionProvider.ts
│ ├── snippetProvider.ts
│ ├── questionsProvider.ts
│ ├── resolver.cn.ts
│ ├── resolver.ts
│ ├── codelensProvider.ts
│ └── memoProvider.ts
├── execTestCode.ts
├── lang
│ ├── bash.ts
│ ├── database.ts
│ ├── common.ts
│ ├── javascript.ts
│ └── typescript.ts
├── memo
│ └── index.ts
├── debugTask
│ └── index.ts
├── cache.ts
├── history
│ ├── storage.ts
│ ├── answer.ts
│ └── index.ts
├── api
│ └── index.ts
├── extension.ts
├── babelPlugin.ts
├── debug
│ └── launch.ts
├── child_process
│ └── execTestCode.ts
├── webview
│ ├── buildCodeWebview.ts
│ └── submitHistory.ts
└── login
│ └── index.ts
├── tsconfig.json
├── .eslintrc.json
└── docs
└── README_zh-CN.md
/template/cpp/algm.h:
--------------------------------------------------------------------------------
1 | #include "ListNode.h"
2 | #include "TreeNode.h"
--------------------------------------------------------------------------------
/images/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/copy.png
--------------------------------------------------------------------------------
/images/memo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/memo.gif
--------------------------------------------------------------------------------
/images/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/test.png
--------------------------------------------------------------------------------
/images/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/build.png
--------------------------------------------------------------------------------
/images/debug.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/debug.gif
--------------------------------------------------------------------------------
/images/debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/debug.png
--------------------------------------------------------------------------------
/images/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/history.png
--------------------------------------------------------------------------------
/images/search.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/search.gif
--------------------------------------------------------------------------------
/images/algorithm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/algorithm.png
--------------------------------------------------------------------------------
/images/shortcut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/shortcut.png
--------------------------------------------------------------------------------
/images/todayRecord.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supperchong/algorithm/HEAD/images/todayRecord.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | .vscode-test/
4 | *.vsix
5 | yarn-error.log
6 | yarn.lock
7 | package-lock.json
8 |
--------------------------------------------------------------------------------
/template/golang/go.mod:
--------------------------------------------------------------------------------
1 | module algm
2 |
3 | go 1.13
4 |
5 | require github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 4,
4 | "semi": false,
5 | "singleQuote": true,
6 | "useTabs": true,
7 | "printWidth": 120
8 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | src/**
5 | .gitignore
6 | vsc-extension-quickstart.md
7 | **/tsconfig.json
8 | **/.eslintrc.json
9 | **/*.map
10 | **/*.ts
11 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/template/golang/go.sum:
--------------------------------------------------------------------------------
1 | github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2 h1:cZqz+yOJ/R64LcKjNQOdARott/jP7BnUQ9Ah7KaZCvw=
2 | github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
3 |
--------------------------------------------------------------------------------
/media/dark/copy.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/media/light/copy.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/template/cpp/ListNode.h:
--------------------------------------------------------------------------------
1 |
2 | //Definition for singly-linked list.
3 | #ifndef _LISTNODE
4 | #define _LISTNODE
5 | struct ListNode {
6 | int val;
7 | ListNode *next;
8 | ListNode() : val(0), next(nullptr) {}
9 | ListNode(int x) : val(x), next(nullptr) {}
10 | ListNode(int x, ListNode *next) : val(x), next(next) {}
11 | };
12 | #endif
--------------------------------------------------------------------------------
/src/model/memo.ts:
--------------------------------------------------------------------------------
1 | import { ResolverParam } from '../provider/resolver'
2 |
3 | interface MemoQuestionFile {
4 | type: 'question'
5 | name: string
6 | param: Partial
7 | }
8 | interface MemoOtherFile {
9 | type: 'other'
10 | name: string
11 | param: string
12 | }
13 | export type MemoFile = MemoQuestionFile | MemoOtherFile
14 | export interface MemoFolder {
15 | name: string
16 | children: MemoFile[]
17 | }
18 |
--------------------------------------------------------------------------------
/template/cpp/TreeNode.h:
--------------------------------------------------------------------------------
1 |
2 | // Definition for a binary tree node.
3 | #ifndef _TREENODE
4 | #define _TREENODE
5 | struct TreeNode
6 | {
7 | int val;
8 | TreeNode *left;
9 | TreeNode *right;
10 | TreeNode() : val(0), left(nullptr), right(nullptr) {}
11 | TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
12 | TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
13 | };
14 | #endif
--------------------------------------------------------------------------------
/media/buildcode.js:
--------------------------------------------------------------------------------
1 | function renderCode(code){
2 | const buildCodeDiv=document.getElementById('buildCode')
3 | buildCodeDiv.innerHTML=code
4 | }
5 | window.addEventListener("message", (event) => {
6 | const message = event.data; // The JSON data our extension sent
7 |
8 | switch (message.command) {
9 | case "newCode": {
10 | renderCode(message.data)
11 | break;
12 | }
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/template/java/ListNode.java:
--------------------------------------------------------------------------------
1 | package algm;
2 |
3 | // Definition for singly-linked list.
4 | public class ListNode {
5 | public int val;
6 | public ListNode next;
7 |
8 | public ListNode() {
9 | }
10 |
11 | public ListNode(int val) {
12 | this.val = val;
13 | }
14 |
15 | public ListNode(int val, ListNode next) {
16 | this.val = val;
17 | this.next = next;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/fileStorage/README.md:
--------------------------------------------------------------------------------
1 | # 存储文件格式
2 |
3 | ## header
4 |
5 | | field | length |
6 | | ------------ | ------ |
7 | | magic number | 32 |
8 | | version | 32 |
9 | | checksum | 32 |
10 | | count | 32 |
11 |
12 | ### section header
13 |
14 | | field | length |
15 | | --------- | ------ |
16 | | checksum | 32 |
17 | | id | 32 |
18 | | fid | 32 |
19 | | offset | 32 |
20 | | size | 16 |
21 | | hash | 128 |
22 | | timestamp | 64 |
23 |
--------------------------------------------------------------------------------
/src/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | import * as vscode from 'vscode'
6 | // import * as myExtension from '../../extension';
7 |
8 | suite('Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.')
10 |
11 | test('Sample test', () => {
12 | assert.equal(-1, [1, 2, 3].indexOf(5))
13 | assert.equal(-1, [1, 2, 3].indexOf(0))
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/template/java/TreeNode.java:
--------------------------------------------------------------------------------
1 | package algm;
2 |
3 | // Definition for a binary tree node.
4 | public class TreeNode {
5 | public int val;
6 | public TreeNode left;
7 | public TreeNode right;
8 |
9 | public TreeNode() {
10 | }
11 |
12 | public TreeNode(int val) {
13 | this.val = val;
14 | }
15 |
16 | public TreeNode(int val, TreeNode left, TreeNode right) {
17 | this.val = val;
18 | this.left = left;
19 | this.right = right;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | "git.rebaseWhenSync": true
12 | }
--------------------------------------------------------------------------------
/src/db.ts:
--------------------------------------------------------------------------------
1 | import Db from './fileStorage/db'
2 | import { config } from './config'
3 | import { Lang } from './model/common'
4 |
5 | const filename = 'question'
6 | const dbMap: Partial> = {}
7 | export async function getDb(): Promise {
8 | const lang = config.lang
9 | const db = dbMap[lang]
10 | if (db) {
11 | return db
12 | } else {
13 | const dbDir = config.dbDir
14 | const db = new Db({
15 | dbDir,
16 | filename: filename,
17 | })
18 | await db.init()
19 | dbMap[lang] = db
20 | return db
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/media/ac.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/website.ts:
--------------------------------------------------------------------------------
1 | import { Lang } from '../model/common'
2 | import { config, DomainCN, DomainEN } from '../config'
3 | export enum Website {
4 | Leetcode = 'https://leetcode.com',
5 | LeetcodeCn = 'https://leetcode.cn',
6 | }
7 | export const WebsiteMap = {
8 | [Lang.cn]: Website.LeetcodeCn,
9 | [Lang.en]: Website.Leetcode,
10 | }
11 | function baseTag(strs: TemplateStringsArray, ...arr: string[]): string {
12 | const baseUrl = WebsiteMap[config.lang]
13 | return baseUrl + '/' + arr.reduce((prev, cur, i) => prev + cur + strs[i + 1], strs[0])
14 | }
15 | export const getSolution = (titleSlug: string): string => baseTag`problems/${titleSlug}/solution/`
16 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | },
19 | {
20 | "type": "npm",
21 | "script": "test",
22 | "group": "test",
23 | "problemMatcher": []
24 | },
25 | {
26 | "type": "npm",
27 | "script": "compile",
28 | "group": "build",
29 | "problemMatcher": []
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 |
3 | import { runTests } from 'vscode-test'
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../')
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index')
14 | // Download VS Code, unzip it and run the integration test
15 | await runTests({ extensionDevelopmentPath, extensionTestsPath })
16 | } catch (err) {
17 | console.error('Failed to run tests')
18 | process.exit(1)
19 | }
20 | }
21 |
22 | main()
23 |
--------------------------------------------------------------------------------
/src/process.ts:
--------------------------------------------------------------------------------
1 | import * as cp from 'child_process'
2 |
3 |
4 | export const processArr: cp.ChildProcess[] = []
5 | export function clearProcessArr() {
6 | processArr.forEach(p => {
7 | p.kill()
8 | })
9 | }
10 |
11 | class ChildProcessProxy {
12 | processIdSet: Set = new Set()
13 | clear() {
14 | this.processIdSet.forEach(pid => {
15 | process.kill(pid)
16 | })
17 | }
18 | add(p: cp.ChildProcess) {
19 | const pid = p.pid
20 | this.processIdSet.add(pid)
21 | p.once('exit', () => {
22 | this.processIdSet.delete(pid)
23 | })
24 | }
25 | remove(p: cp.ChildProcess | null) {
26 | if (!p) {
27 | return
28 | }
29 | if (this.processIdSet.has(p.pid)) {
30 | p.kill()
31 | }
32 | }
33 | }
34 |
35 | export const childProcessProxy = new ChildProcessProxy()
--------------------------------------------------------------------------------
/src/common/lang.ts:
--------------------------------------------------------------------------------
1 | import { CaseList } from './util'
2 |
3 | export type MetaData = LanguageMetaData | DataBaseMetaData | ShellMetaData
4 | interface Param {
5 | name: string
6 | type: string
7 | }
8 | interface ReturnType {
9 | type: string
10 | }
11 | export interface LanguageMetaData {
12 | name: string
13 | params: Param[]
14 | return: ReturnType
15 | }
16 | export interface DataBaseMetaData {
17 | database: true
18 | mssql: string[]
19 | mysql: string[]
20 | oraclesql: string[]
21 | }
22 | export interface ShellMetaData {
23 | shell: true
24 | }
25 |
26 | export interface TestOptions {
27 | caseList: CaseList
28 | metaData: LanguageMetaData
29 | filePath: string
30 | originCode: string
31 | }
32 | export type DebugOptions = Omit
33 | export const defaultTimeout = 10000
34 |
--------------------------------------------------------------------------------
/src/fileStorage/util.ts:
--------------------------------------------------------------------------------
1 | import crypto = require('crypto')
2 | import crc32_table from './crc32_table'
3 | export function md5(data: crypto.BinaryLike): string {
4 | const hash = crypto.createHash('md5')
5 | return hash.update(data).digest('hex')
6 | }
7 |
8 | /**
9 | * Reference:https://docs.microsoft.com/en-us/openspecs/office_protocols/ms-abs/06966aa2-70da-4bf9-8448-3355f277cd77?redirectedfrom=MSDN
10 | * @param buffer
11 | */
12 | export function crc32(buffer: Buffer): number {
13 | let crc = 0xffffffff
14 | let i = 0
15 | while (i < buffer.length) {
16 | crc = crc32_table[(crc & 0xff) ^ buffer[i]] ^ ((crc >= 0 ? crc : 2 ** 32 + crc) / 256)
17 | i++
18 | }
19 | crc = crc ^ 0xffffffff
20 | return crc >= 0 ? crc : 2 ** 32 + crc
21 | }
22 | export function verify(data: Buffer, checksum: number) {
23 | return crc32(data) === checksum
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": [
7 | "es6"
8 | ],
9 | "useUnknownInCatchVariables":false,
10 | "sourceMap": true,
11 | "noImplicitAny": false,
12 | "noImplicitThis": false,
13 | "rootDir": "src",
14 | "strict": true /* enable all strict type-checking options */
15 | /* Additional Checks */
16 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
17 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
18 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
19 | },
20 | "exclude": [
21 | "node_modules",
22 | ".vscode-test"
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as Mocha from 'mocha'
3 | import * as glob from 'glob'
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd',
9 | color: true,
10 | })
11 |
12 | const testsRoot = path.resolve(__dirname, '..')
13 |
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err)
18 | }
19 |
20 | // Add files to the test suite
21 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)))
22 |
23 | try {
24 | // Run the mocha test
25 | mocha.run((failures) => {
26 | if (failures > 0) {
27 | e(new Error(`${failures} tests failed.`))
28 | } else {
29 | c()
30 | }
31 | })
32 | } catch (err) {
33 | console.error(err)
34 | e(err)
35 | }
36 | })
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/src/provider/completionProvider.ts:
--------------------------------------------------------------------------------
1 | import { config } from '../config'
2 | import * as vscode from 'vscode'
3 |
4 | const defaultCompletionItems = [
5 | {
6 | languages: ['javascript', 'typescript'],
7 | prefix: 'algorithm',
8 | body: "// @algorithm\r\nimport * as a from '" + config.algmModuleDir + "'\r\n\r\n",
9 | },
10 | ]
11 | export function registerCompletionItemProvider(context: vscode.ExtensionContext) {
12 | defaultCompletionItems.forEach(({ languages, prefix, body }) => {
13 | context.subscriptions.push(
14 | vscode.languages.registerCompletionItemProvider(
15 | languages,
16 | {
17 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
18 | provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
19 | const snippetCompletion = new vscode.CompletionItem(prefix)
20 | snippetCompletion.insertText = new vscode.SnippetString(body)
21 |
22 | return [snippetCompletion]
23 | },
24 | },
25 | '.' // triggered whenever a '.' is being typed
26 | )
27 | )
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": [
14 | "--extensionDevelopmentPath=${workspaceFolder}"
15 | ],
16 | "outFiles": [
17 | "${workspaceFolder}/out/**/*.js"
18 | ],
19 | // "preLaunchTask": "${defaultBuildTask}"
20 | },
21 | {
22 | "name": "Extension Tests",
23 | "type": "extensionHost",
24 | "request": "launch",
25 | "runtimeExecutable": "${execPath}",
26 | "args": [
27 | "--extensionDevelopmentPath=${workspaceFolder}",
28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
29 | ],
30 | "outFiles": [
31 | "${workspaceFolder}/out/test/**/*.js"
32 | ],
33 | "preLaunchTask": "${defaultBuildTask}"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/unit/test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | // import * as myExtension from '../../extension';
6 | import { getTestCaseList } from '../../common/util'
7 | const cases = [
8 | `// @algorithm @lc id=1576 lang=javascript weekname=weekly-contest-191
9 | // @title reorder-routes-to-make-all-paths-lead-to-the-city-zero
10 |
11 | // @test(6,[[0,1],[1,3],[2,3],[4,0],[4,5]])=3
12 | // @test(5,[[1,0],[1,2],[3,2],[3,4]])=2
13 | // @test(3,[[1,0],[2,0]])=0
14 | /**
15 | * @param {number} n
16 | * @param {number[][]} connections
17 | * @return {number}
18 | */
19 | var minReorder = function (n, connections) {
20 | }
21 | `,
22 | ]
23 | suite('Util Test Suite', () => {
24 | test('test getTestCaseList', () => {
25 | const testCaseList = getTestCaseList(cases[0])
26 | assert.equal(testCaseList.length, 1)
27 | const { testCase } = testCaseList[0]
28 | assert.deepEqual(testCase, [
29 | '// @test(6,[[0,1],[1,3],[2,3],[4,0],[4,5]])=3',
30 | '// @test(5,[[1,0],[1,2],[3,2],[3,4]])=2',
31 | '// @test(3,[[1,0],[2,0]])=0',
32 | ])
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/src/model/command.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { TestCaseParam } from '../common/util'
3 | import { QuestionsProvider } from '../provider/questionsProvider'
4 |
5 | export interface CodeLensesOptions {
6 | text: string
7 | filePath: string
8 | langSlug: string
9 | }
10 | export interface TestCodeLensesOptions {
11 | filePath: string
12 | testCaseParam: TestCaseParam
13 | }
14 | export type BuildCodeLensesOptions = CodeLensesOptions
15 | export type SubmitCodeLensesOptions = CodeLensesOptions
16 | export type GetDescriptionCodeLensesOptions = CodeLensesOptions
17 | export type ViewSubmitHistoryCodeLensesOptions = CodeLensesOptions
18 | type FilePath = string
19 | export type DebugLensesOptions = FilePath
20 | type ExtensionPath = string
21 | export type BuildCodeArguments = [vscode.ExtensionContext, BuildCodeLensesOptions]
22 | export type TestCodeArguments = [TestCodeLensesOptions]
23 | export type DebugCodeArguments = [DebugLensesOptions]
24 | export type SubmitCodeArguments = [QuestionsProvider, SubmitCodeLensesOptions]
25 | export type GetDescriptionArguments = [ExtensionPath, GetDescriptionCodeLensesOptions]
26 | export type ViewSubmitHistoryArguments = [vscode.ExtensionContext, CodeLensesOptions]
27 |
--------------------------------------------------------------------------------
/src/execTestCode.ts:
--------------------------------------------------------------------------------
1 | import cp = require('child_process')
2 |
3 | import path = require('path')
4 | import { config } from './config'
5 | import { window } from 'vscode'
6 | import { TestOptions } from './common/lang'
7 | const notFoundReg = /node: not found/
8 | export function execTestChildProcess(options: TestOptions): Promise {
9 | return new Promise((resolve) => {
10 | const { nodeBinPath } = config
11 | const cmd = cp.spawn(nodeBinPath, [path.join(__dirname, './child_process/execTestCode.js')])
12 | let msg = ''
13 | cmd.stdout.on('data', (data) => {
14 | console.log('data', data)
15 | msg += data
16 | })
17 | cmd.stdin.setDefaultEncoding('utf8')
18 | cmd.stdin.write(JSON.stringify(options))
19 | cmd.stdin.end()
20 | // cmd.send(JSON.stringify(options))
21 | cmd.stderr.on('data', (message) => {
22 | console.log('err', message.toString())
23 | if (notFoundReg.test(message)) {
24 | window.showErrorMessage(message + ',please set the path of the nodejs')
25 | } else {
26 | window.showErrorMessage(message)
27 | }
28 | })
29 | cmd.on('close', () => {
30 | resolve(msg)
31 | console.log('child process close')
32 | console.log('msg', msg)
33 | })
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/src/lang/bash.ts:
--------------------------------------------------------------------------------
1 | import { log } from '../config'
2 | import { OutputChannel } from 'vscode'
3 | import { readFileAsync } from '../common/util'
4 | import { getFuncNames } from '../common/util'
5 |
6 | export class BashParse {
7 | public log: OutputChannel
8 | public originCode?: string
9 | public commentToken = '#'
10 | constructor(public filePath: string, public text?: string) {
11 | this.log = log
12 | if (!filePath) {
13 | throw new Error('filePath must not empty')
14 | }
15 | if (text) {
16 | this.originCode = text
17 | }
18 | }
19 | async getOriginCode() {
20 | if (this.originCode) {
21 | return this.originCode
22 | }
23 | const originCode = await readFileAsync(this.filePath, {
24 | encoding: 'utf8',
25 | })
26 | this.originCode = originCode
27 | return this.originCode
28 | }
29 | public async buildCode() {
30 | const originCode = await this.getOriginCode()
31 | const { questionMeta } = getFuncNames(originCode, this.filePath)
32 |
33 | const code = originCode
34 | .split('\n')
35 | .filter((line) => !this.isComment(line))
36 | .join('\n')
37 | return {
38 | code,
39 | questionMeta,
40 | }
41 | }
42 | isComment(line: string): boolean {
43 | return line.trim().startsWith('#')
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/lang/database.ts:
--------------------------------------------------------------------------------
1 | import { log } from '../config'
2 | import { OutputChannel } from 'vscode'
3 | import { readFileAsync } from '../common/util'
4 | import { getFuncNames } from '../common/util'
5 |
6 | export class DataBaseParse {
7 | public log: OutputChannel
8 | public originCode?: string
9 | public commentToken = '#'
10 | constructor(public filePath: string, public text?: string) {
11 | this.log = log
12 | if (!filePath) {
13 | throw new Error('filePath must not empty')
14 | }
15 | if (text) {
16 | this.originCode = text
17 | }
18 | }
19 | async getOriginCode() {
20 | if (this.originCode) {
21 | return this.originCode
22 | }
23 | const originCode = await readFileAsync(this.filePath, {
24 | encoding: 'utf8',
25 | })
26 | this.originCode = originCode
27 | return this.originCode
28 | }
29 | public async buildCode() {
30 | const originCode = await this.getOriginCode()
31 | const { questionMeta } = getFuncNames(originCode, this.filePath)
32 |
33 | const code = originCode
34 | .split('\n')
35 | .filter((line) => !this.isComment(line))
36 | .join('\n')
37 | return {
38 | code,
39 | questionMeta,
40 | }
41 | }
42 | isComment(line: string): boolean {
43 | return line.trim().startsWith('#')
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/unit/fileStorage.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | // import * as myExtension from '../../extension';
6 | import { crc32 } from '../../fileStorage/util'
7 | import { convertToObj, convertToBuffer, Field } from '../../fileStorage/db'
8 | interface FieldData {
9 | v: number
10 | h: string
11 | }
12 | suite('test file storage', () => {
13 | test('test crc32', () => {
14 | const data = Buffer.from([0x61, 0x62, 0x63])
15 |
16 | const result = crc32(data)
17 | assert.equal(result, 0x352441c2)
18 | })
19 | test('test convertToObj', () => {
20 | const config: Field[] = [
21 | {
22 | field: 'v',
23 | type: 'int',
24 | len: 16,
25 | },
26 | {
27 | field: 'h',
28 | type: 'hex',
29 | len: 32,
30 | },
31 | ]
32 | const buffer = Buffer.from([0x00, 0x11, 0x00, 0x12, 0xab, 0xcd])
33 | const result = convertToObj(buffer, config)
34 | assert.equal(result.v, 17)
35 | assert.equal(result.h, '0012abcd')
36 | })
37 | test('test convertToBuffer', () => {
38 | const config: Field[] = [
39 | {
40 | field: 'v',
41 | type: 'int',
42 | len: 16,
43 | },
44 | {
45 | field: 'h',
46 | type: 'hex',
47 | len: 32,
48 | },
49 | ]
50 | const obj: FieldData = {
51 | v: 17,
52 | h: '0012abcd',
53 | }
54 | const buffer = Buffer.from([0x00, 0x11, 0x00, 0x12, 0xab, 0xcd])
55 | const result = convertToBuffer(obj, config)
56 | assert.deepEqual(result, buffer)
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/src/common/transformCode.ts:
--------------------------------------------------------------------------------
1 | import * as recast from 'recast'
2 | import * as acorn from 'acorn'
3 | import { builders, namedTypes } from 'ast-types'
4 | import { FunctionDeclarationKind, VariableDeclarationKind } from 'ast-types/gen/kinds'
5 |
6 | function addAstComment(ast: namedTypes.File, comment: string, funName: string) {
7 | const visitor = {
8 | FunctionDeclaration: (node: FunctionDeclarationKind) => {
9 | if (node.id && node.id.name === funName) {
10 | insertLineComment(node, comment)
11 | }
12 | },
13 | VariableDeclaration: (node: VariableDeclarationKind) => {
14 | if (
15 | node.declarations.find((declaration) => {
16 | return (
17 | declaration.type === 'VariableDeclarator' &&
18 | declaration.id.type === 'Identifier' &&
19 | declaration.id.name === funName
20 | )
21 | })
22 | ) {
23 | insertLineComment(node, comment)
24 | }
25 | },
26 | }
27 | const body = ast.program.body
28 | for (const node of body) {
29 | const fn = visitor[node.type]
30 | if (fn) {
31 | fn(node)
32 | }
33 | }
34 | }
35 | function insertLineComment(node: VariableDeclarationKind | FunctionDeclarationKind, comment: string) {
36 | const commentNode = builders.commentLine(comment, true)
37 | const originComments = node.comments || []
38 | const mergeComments = [commentNode, ...originComments]
39 |
40 | node.comments = mergeComments
41 | }
42 | export function addComment(source: string, comment: string, funName: string) {
43 | const ast: namedTypes.File = recast.parse(source, {
44 | parser: acorn,
45 | })
46 | addAstComment(ast, comment, funName)
47 | const output = recast.print(ast).code
48 | return output
49 | }
50 |
--------------------------------------------------------------------------------
/src/provider/snippetProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { setLinePrefix } from '../common/util'
3 | import { tag } from 'pretty-tag'
4 | export function registerForSnippetProviders(context: vscode.ExtensionContext) {
5 | const forVariables = ['i', 'j', 'k']
6 | const fns = forVariables.map((forVariable) => {
7 | return function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
8 | const key = `for${forVariable}.`
9 | let text = document.lineAt(position).text
10 | if (!text.endsWith(key)) {
11 | return
12 | }
13 | let whitePrefix = ''
14 | for (let i = 0; i < text.length; i++) {
15 | if (!/\s/.test(text[i])) {
16 | whitePrefix = text.slice(0, i)
17 | break
18 | }
19 | }
20 | text = text.trimLeft()
21 | const snippetCompletion = new vscode.CompletionItem(text)
22 | const range = document.lineAt(position).range
23 | snippetCompletion.range = range
24 | const prefix = text.slice(0, text.length - 5)
25 | let snippetString = tag`
26 | for (let ${forVariable} = 0; ${forVariable} < ${prefix}.length; ${forVariable}++) {
27 | let element = ${prefix}[${forVariable}]
28 | $0
29 | }`
30 | snippetString = setLinePrefix(snippetString, whitePrefix)
31 | snippetCompletion.insertText = new vscode.SnippetString(snippetString)
32 | return [snippetCompletion]
33 | }
34 | })
35 | fns.forEach((fn) => {
36 | context.subscriptions.push(
37 | vscode.languages.registerCompletionItemProvider(
38 | { language: 'javascript', scheme: 'file' },
39 | { provideCompletionItems: fn },
40 | '.'
41 | )
42 | )
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint",
10 | "prettier"
11 | ],
12 | "extends": [
13 | "eslint:recommended",
14 | "plugin:@typescript-eslint/eslint-recommended",
15 | "plugin:@typescript-eslint/recommended",
16 | "prettier"
17 | ],
18 | "rules": {
19 | "@typescript-eslint/semi": "off",
20 | "@typescript-eslint/explicit-function-return-type": "off",
21 | "@typescript-eslint/camelcase": ["off"],
22 | "@typescript-eslint/no-use-before-define": ["error", { "functions": false }],
23 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
24 | "@typescript-eslint/no-non-null-assertion":["off"],
25 | "@typescript-eslint/explicit-module-boundary-types":["off"],
26 | "@typescript-eslint/naming-convention": [
27 | "error",
28 | {
29 | "selector": [
30 | "classProperty",
31 | "objectLiteralProperty",
32 | "typeProperty",
33 | "classMethod",
34 | "objectLiteralMethod",
35 | "typeMethod",
36 | "accessor",
37 | "enumMember"
38 | ],
39 | "format": null,
40 | "modifiers": ["requiresQuotes"]
41 | }
42 | ],
43 | "curly": "error",
44 | "eqeqeq": "error",
45 | "no-throw-literal": "error",
46 | "semi": [
47 | "error",
48 | "never"
49 | ],
50 | "indent": [
51 | "error",
52 | "tab",
53 | {
54 | "SwitchCase": 1,
55 | "ignoredNodes": [
56 | "ConditionalExpression"
57 | ]
58 | }
59 | ],
60 | "prefer-const": [
61 | "error",
62 | {
63 | "destructuring": "all"
64 | }
65 | ]
66 | }
67 | }
--------------------------------------------------------------------------------
/src/memo/index.ts:
--------------------------------------------------------------------------------
1 | import { existDir, writeFileAsync } from '../common/util'
2 | import { config, updateEnv } from '../config'
3 | import * as path from 'path'
4 | import { ResolverParam } from '../provider/resolver'
5 | export function addFolder(name: string) {
6 | if (config.env.memo.find((v) => v.name === name)) {
7 | throw new Error('folder already exists')
8 | }
9 | config.env.memo.push({
10 | name,
11 | children: [],
12 | })
13 | updateEnv('memo', config.env.memo)
14 | }
15 | export function deleteFolder(name: string) {
16 | const memo = config.env.memo
17 | const index = memo.findIndex((item) => item.name === name)
18 | memo.splice(index, 1)
19 | updateEnv('memo', memo)
20 | }
21 | export async function addFolderFile(folderName: string, fileName: string) {
22 | const memo = config.env.memo
23 | const folder = memo.find((item) => item.name === folderName)
24 | if (folder) {
25 | folder.children.push({
26 | type: 'other',
27 | name: fileName,
28 | param: fileName,
29 | })
30 | const memoDir = path.join(config.cacheBaseDir, 'memo')
31 | const filePath = path.join(memoDir, fileName)
32 | await existDir(memoDir)
33 | await writeFileAsync(filePath, Buffer.alloc(0))
34 | }
35 | }
36 | export function addQuestion(folderName: string, name: string, param: Partial) {
37 | const memo = config.env.memo
38 | const folder = memo.find((item) => item.name === folderName)
39 | if (folder) {
40 | if (folder.children.find((v) => v.name === name)) {
41 | throw new Error('file already exists')
42 | }
43 | folder.children.push({
44 | type: 'question',
45 | name,
46 | param,
47 | })
48 | updateEnv('memo', memo)
49 | }
50 | }
51 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
52 | async function deleteFile(folderName: string, fileName: string) {
53 | const memo = config.env.memo
54 | const folder = memo.find((item) => item.name === folderName)
55 | if (folder) {
56 | const index = folder.children.findIndex((c) => c.name === fileName)
57 | if (index !== -1) {
58 | folder.children.splice(index, 1)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/media/algorithm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/media/history.css:
--------------------------------------------------------------------------------
1 | input:disabled {
2 | border-color: rgba(0, 0, 0, 0);
3 | background-color: rgba(0, 0, 0, 0);
4 | color: var(--vscode-list-inactiveSelectionForeground);
5 | }
6 | input {
7 | text-align: center;
8 | }
9 | .container {
10 | margin-top: 10px;
11 | height: 50vh;
12 | border-bottom: 1px solid #aaa;
13 | overflow: scroll;
14 | }
15 | .hide {
16 | display: none;
17 | }
18 | .history-code {
19 | overflow: scroll;
20 | }
21 | .nav {
22 | display: flex;
23 | }
24 |
25 | .nav button {
26 | margin-right: 20px;
27 | }
28 | .nav button.active {
29 | background-color: var(--vscode-pickerGroup-foreground);
30 | border-color: var(--vscode-pickerGroup-foreground);
31 | border-style: none;
32 | outline: none;
33 | }
34 | .nav button:focus {
35 | outline: none;
36 | }
37 | .item,
38 | .item-header {
39 | display: flex;
40 | flex-direction: row;
41 | /* justify-content: space-around; */
42 | height: 30px;
43 | border-bottom: 1px solid #eee;
44 | align-items: center;
45 | margin-left: 10px;
46 | margin-right: 10px;
47 | font-size: 16px;
48 | border-top: 1px solid #eee;
49 | }
50 |
51 | .item > div,
52 | .item-header > div {
53 | flex: 1;
54 | text-align: center;
55 | }
56 | .item input {
57 | width: 100%;
58 | }
59 | .item {
60 | font-size: 14px;
61 | cursor: pointer;
62 | color: var(--vscode-list-inactiveSelectionForeground);
63 | background-color: var(--vscode-list-inactiveSelectionBackground);
64 | }
65 |
66 | .item:hover {
67 | background-color: var(--vscode-list-hoverBackground);
68 | color: var(--vscode-list-hoverForeground);
69 | }
70 |
71 | .item.active {
72 | background-color: var(--vscode-list-activeSelectionBackground);
73 | color: var(--vscode-list-activeSelectionForeground);
74 | }
75 |
76 | .item > div {
77 | opacity: 0.7;
78 | }
79 | .operate-box {
80 | display: flex;
81 | justify-content: space-evenly;
82 | color: var(--vscode-editorLightBulb-foreground);
83 | }
84 |
85 | body.vscode-light .operate-box {
86 | color: var(--vscode-editorLink-activeForeground);
87 | }
88 | body.vscode-light .active .operate-box {
89 | color: var(--vscode-list-activeSelectionForeground);
90 | }
91 |
--------------------------------------------------------------------------------
/src/lang/common.ts:
--------------------------------------------------------------------------------
1 | import { CodeLang, getFileLang, ExtraType } from '../common/langConfig'
2 | import { BaseLang } from './base'
3 | import { PythonParse } from './python'
4 | import { GoParse } from './golang'
5 | import { JavaParse } from './java'
6 | import { CppParse } from './cpp'
7 | import { TestCase } from '../common/util'
8 | import * as vscode from 'vscode'
9 | import * as path from 'path'
10 | import { enableLang } from '../common/langConfig'
11 | import { JavascriptParse } from './javascript'
12 | import { TypescriptParse } from './typescript'
13 | const langMap = {
14 | [CodeLang.Python3]: PythonParse,
15 | [CodeLang.Go]: GoParse,
16 | [CodeLang.Java]: JavaParse,
17 | [CodeLang['C++']]: CppParse,
18 | [CodeLang.JavaScript]: JavascriptParse,
19 | [CodeLang.TypeScript]: TypescriptParse,
20 | }
21 | export class Service {
22 | public codeLang: CodeLang
23 | private ctx: BaseLang
24 | constructor(public filePath: string, public text?: string) {
25 | this.codeLang = getFileLang(filePath)
26 |
27 | const Parse = langMap[this.codeLang]
28 | if (!Parse) {
29 | const fileParse = path.parse(filePath)
30 | throw new Error('Currently, not support ' + fileParse.ext)
31 | }
32 | this.ctx = new Parse(filePath, text)
33 | }
34 | static getPreImport(codeLang: CodeLang, name: string, extraTypeSet: Set): string {
35 | const Parse = langMap[codeLang]
36 | if (!Parse) {
37 | return ''
38 | }
39 | return Parse.getPreImport(name, extraTypeSet)
40 | }
41 | static handlePreImport(filePath: string) {
42 | const codeLang = getFileLang(filePath)
43 | if (['JavaScript', 'TypeScript'].includes(codeLang)) {
44 | return
45 | }
46 | const service = new Service(filePath)
47 | return service.ctx.handlePreImport()
48 | }
49 | public isSupport() {
50 | return enableLang.includes(this.codeLang)
51 | }
52 | public execTest(testCase: TestCase) {
53 | return this.ctx.execTest(testCase)
54 | }
55 | public debugCodeCommand(folder: vscode.WorkspaceFolder, breaks: vscode.SourceBreakpoint[]) {
56 | return this.ctx.debugCodeCommand(folder, breaks)
57 | }
58 | public buildCode() {
59 | return this.ctx.buildCode()
60 | }
61 | public getTestCaseList(text: string) {
62 | return this.ctx.getTestCaseList(text)
63 | }
64 | public addComment(text, comment, funcName) {
65 | return this.ctx.addComment(text, comment, funcName)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/debugTask/index.ts:
--------------------------------------------------------------------------------
1 | import { writeFileSync, promises } from 'fs'
2 | import { outBoundArrayPlugin } from '../babelPlugin'
3 | import rollup = require('rollup')
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import rollupBabelPlugin from '@rollup/plugin-babel'
6 | import * as path from 'path'
7 | import { CodeLang, getFileLang } from '../common/langConfig'
8 | import { DebugOptions } from '../common/lang'
9 | import { handleArgsType } from '../common/util'
10 |
11 | export async function main(options: DebugOptions, args: string[]) {
12 | const mainFilePath = options.filePath
13 | const dir = path.resolve(mainFilePath, '..', '..', '..')
14 |
15 | const outputDir = path.join(dir, 'out')
16 | const codeLang = getFileLang(mainFilePath)
17 | if (![CodeLang.JavaScript, CodeLang.TypeScript].includes(codeLang)) {
18 | console.log('only support JavaScript and TypeScript')
19 | return
20 | }
21 | await promises.mkdir(outputDir, { recursive: true })
22 | const codePath = path.join(outputDir, 'code.js')
23 | const codeMapPath = path.join(outputDir, 'code.js.map')
24 |
25 | const mainFileCode = options.originCode
26 |
27 | const meta = options.metaData
28 | const funName = meta.name
29 |
30 | if (codeLang === CodeLang.TypeScript) {
31 | const finalCode = mainFileCode + '\n' + handleArgsType(meta, '', args, true)
32 | return buildTsCode(finalCode, mainFilePath, path.join(dir, 'out'))
33 | }
34 |
35 | const bundle = await rollup.rollup({
36 | input: mainFilePath,
37 | treeshake: false,
38 | plugins: [
39 | resolve(),
40 | rollupBabelPlugin({
41 | babelHelpers: 'bundled',
42 | comments: false,
43 | plugins: [outBoundArrayPlugin],
44 | }),
45 | ],
46 | })
47 | const { output } = await bundle.generate({
48 | sourcemap: true,
49 | sourcemapPathTransform: (r, s) => {
50 | return path.join(path.parse(s).dir, r)
51 | },
52 | })
53 | let code = output[0].code
54 | const map = output[0].map
55 | if (funName) {
56 | code += handleArgsType(meta, '', args) + '\n'
57 | }
58 |
59 | code = code + '//# sourceMappingURL=code.js.map'
60 | if (map?.file) {
61 | map.file = 'code.js'
62 | }
63 |
64 | writeFileSync(codePath, code)
65 | writeFileSync(codeMapPath, JSON.stringify(map))
66 | }
67 |
68 | export async function buildTsCode(text: string, filePath: string, dir: string) {
69 | // eslint-disable-next-line @typescript-eslint/no-var-requires
70 | return require('esbuild')
71 | .build({
72 | stdin: {
73 | contents: text,
74 | loader: 'ts',
75 | resolveDir: dir,
76 | sourcefile: filePath,
77 | },
78 | platform: 'node',
79 | mainFields: ['module', 'main'],
80 | bundle: true,
81 | format: 'esm',
82 | treeShaking: true,
83 | outfile: 'code.js',
84 | absWorkingDir: dir,
85 | sourcemap: 'inline',
86 | })
87 | .catch((err) => {
88 | console.log(err)
89 | })
90 | }
91 |
--------------------------------------------------------------------------------
/src/fileStorage/crc32_table.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * copy from https://docs.microsoft.com/en-us/openspecs/office_protocols/ms-abs/06966aa2-70da-4bf9-8448-3355f277cd77?redirectedfrom=MSDN
3 | */
4 |
5 | const crc32_table: number[] = [
6 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
7 | 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
8 | 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
9 | 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
10 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
11 | 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
12 | 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
13 | 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
14 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
15 | 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
16 | 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
17 | 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
18 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
19 | 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
20 | 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
21 | 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
22 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
23 | 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
24 | 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
25 | 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
26 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
27 | 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
28 | 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
29 | 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
30 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
31 | 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
32 | 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330,
33 | 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
34 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
35 | ]
36 | export default crc32_table
37 |
--------------------------------------------------------------------------------
/src/cache.ts:
--------------------------------------------------------------------------------
1 | import { ConciseQuestion, MapIdConciseQuestion, Tag } from './model/common'
2 | import fs = require('fs')
3 | import { config, Config, log } from './config'
4 | import { reRequire } from './util'
5 | const COOKIE = 'cookie'
6 | const TAG = 'tag'
7 | export const ALLQUESTIONS = 'allQuestions'
8 | export const MAPIDQUESTION = 'MapIdQuestion'
9 | interface CacheMap {
10 | [COOKIE]: Cookie
11 | [TAG]: Tag[]
12 | [ALLQUESTIONS]: ConciseQuestion[]
13 | [MAPIDQUESTION]: MapIdConciseQuestion
14 | [titleSlug: string]: any
15 | }
16 | interface Cookie {
17 | cookie: string
18 | 'x-csrftoken': string
19 | }
20 | class Cache {
21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
22 | private cacheMap: Partial = {}
23 | config: Config
24 | constructor() {
25 | this.config = config
26 | }
27 | removeCache() {
28 | this.cacheMap = {}
29 | }
30 | get(key: T) {
31 | return this.cacheMap[key]
32 | }
33 | set(key: T, value: R) {
34 | this.cacheMap[key] = value
35 | }
36 | has(key: string) {
37 | return key in this.cacheMap
38 | }
39 | async getCookie() {
40 | const { cookiePath } = config
41 | if (this.has(COOKIE)) {
42 | return this.get(COOKIE)
43 | }
44 | try {
45 | const cookie = reRequire(cookiePath)
46 | this.set(COOKIE, cookie)
47 | return this.get(COOKIE)
48 | } catch (err) {
49 | return null
50 | }
51 | }
52 | getQuestions(): ConciseQuestion[] {
53 | const { questionPath } = config
54 | if (this.has(ALLQUESTIONS)) {
55 | return this.get(ALLQUESTIONS) || []
56 | }
57 | try {
58 | const allQuestions = reRequire(questionPath)
59 | let filterPrivilegeQuestions = allQuestions
60 | if (!config.displayLock) {
61 | filterPrivilegeQuestions = allQuestions.filter((v) => !v.paid_only)
62 | }
63 | this.set(ALLQUESTIONS, filterPrivilegeQuestions)
64 | return this.get(ALLQUESTIONS) || []
65 | } catch (err) {
66 | return []
67 | }
68 | }
69 |
70 | setQuestions(questions: ConciseQuestion[]) {
71 | const { questionPath } = config
72 | let filterPrivilegeQuestions = questions
73 | if (!config.displayLock) {
74 | filterPrivilegeQuestions = questions.filter((v) => !v.paid_only)
75 | }
76 | this.set(ALLQUESTIONS, filterPrivilegeQuestions)
77 | fs.writeFile(
78 | questionPath,
79 | JSON.stringify(filterPrivilegeQuestions),
80 | {
81 | encoding: 'utf8',
82 | },
83 | (err) => {
84 | if (err) {
85 | log.append(err.message)
86 | }
87 | }
88 | )
89 | }
90 | updateQuestion(id: number, params: Partial) {
91 | const questions: ConciseQuestion[] | undefined = this.get(ALLQUESTIONS)
92 | if (questions) {
93 | const question = questions.find((q) => q.id === id)
94 | if (question) {
95 | Object.assign(question, params)
96 | this.setQuestions(questions)
97 | }
98 | }
99 | }
100 | getTags() {
101 | const { tagPath } = config
102 | if (this.has(TAG)) {
103 | return this.get(TAG)
104 | }
105 | try {
106 | const tags = reRequire(tagPath)
107 | this.set(TAG, tags)
108 | return this.get(TAG)
109 | } catch (err) {
110 | return null
111 | }
112 | }
113 | setTags(tags: Tag[]) {
114 | const { tagPath } = config
115 | this.set(TAG, tags)
116 | fs.writeFile(
117 | tagPath,
118 | JSON.stringify(tags),
119 | {
120 | encoding: 'utf8',
121 | },
122 | (err) => {
123 | if (err) {
124 | log.append(err.message)
125 | }
126 | }
127 | )
128 | }
129 | }
130 | export const cache = new Cache()
131 |
--------------------------------------------------------------------------------
/src/provider/questionsProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { QuestionPreview } from '../webview/questionPreview'
3 | import { config } from '../config'
4 | import { resolverEn, ResolverFn, ResolverParam, ResolverType } from './resolver'
5 | import { resolverCn } from './resolver.cn'
6 | import * as path from 'path'
7 | export class QuestionTree extends vscode.TreeItem {
8 | constructor(
9 | public readonly label: string,
10 | public readonly id: string,
11 | public readonly collapsibleState: vscode.TreeItemCollapsibleState,
12 | public readonly type: string,
13 | public readonly key?: string,
14 | public readonly param?: Partial,
15 | public readonly command?: vscode.Command
16 | ) {
17 | super(label, collapsibleState)
18 | }
19 | }
20 | export class QuestionsProvider implements vscode.TreeDataProvider {
21 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter<
22 | QuestionTree | undefined
23 | >()
24 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event
25 | constructor(
26 | private workspaceRoot: ReadonlyArray | undefined,
27 | private _extensionPath: string
28 | ) {}
29 | refresh(): void {
30 | this._onDidChangeTreeData.fire(undefined)
31 | }
32 |
33 | getTreeItem(element: QuestionTree): vscode.TreeItem {
34 | return element
35 | }
36 | getChildren(element?: QuestionTree): Thenable {
37 | let resolver: ResolverType
38 | if (config.lang === 'en') {
39 | resolver = resolverEn
40 | } else {
41 | resolver = resolverCn
42 | }
43 | if (element) {
44 | const type = element.type
45 | const key = element.key
46 | const param = element.param || {}
47 | let fn: ResolverFn
48 | if (key) {
49 | fn = resolver[type][key]
50 | } else {
51 | fn = resolver[type] as ResolverFn
52 | }
53 | return Promise.resolve(fn(param))
54 | .then((arr) => {
55 | if (!arr.length) {
56 | return []
57 | }
58 | const isLastChild = arr[0].isLast
59 | if (isLastChild) {
60 | return arr.map((v) => {
61 | const dep = new QuestionTree(
62 | v.label,
63 | v.id,
64 | vscode.TreeItemCollapsibleState.None,
65 | v.type,
66 | v.key,
67 | v.param,
68 | {
69 | title: 'QuestionPreview',
70 | command: QuestionPreview,
71 | arguments: [v.param],
72 | }
73 | )
74 | if (v.paidOnly) {
75 | dep.iconPath = new vscode.ThemeIcon('lock')
76 | } else {
77 | // dep.iconPath = v.isAC ? new vscode.ThemeIcon('check') : ''
78 | dep.iconPath = v.isAC ? path.join(__dirname, '..', '..', 'media', 'ac.svg') : ''
79 | }
80 | dep.contextValue = 'memo'
81 | return dep
82 | })
83 | } else {
84 | return arr.map(
85 | (v) =>
86 | new QuestionTree(
87 | v.label,
88 | v.id,
89 | vscode.TreeItemCollapsibleState.Collapsed,
90 | v.type,
91 | v.key,
92 | v.param || {}
93 | )
94 | )
95 | }
96 | })
97 | .catch((err) => {
98 | console.log(err)
99 | return []
100 | })
101 | } else {
102 | return Promise.resolve(resolver.Query()).then((arr) =>
103 | arr.map(
104 | (v) =>
105 | new QuestionTree(
106 | v.label,
107 | v.id,
108 | vscode.TreeItemCollapsibleState.Collapsed,
109 | v.type,
110 | v.key,
111 | v.param || {}
112 | )
113 | )
114 | )
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/test/suite/util.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | // import * as myExtension from '../../extension';
6 | import { getTestCaseList, unionArr } from '../../common/util'
7 | import { addComment } from '../../common/transformCode'
8 |
9 | interface UnionArrDemo {
10 | params: T[][]
11 | result: T[]
12 | }
13 | const cases = [
14 | `// @algorithm @lc id=1576 lang=javascript weekname=weekly-contest-191
15 | // @title reorder-routes-to-make-all-paths-lead-to-the-city-zero
16 |
17 | // @test(6,[[0,1],[1,3],[2,3],[4,0],[4,5]])=3
18 | // @test(5,[[1,0],[1,2],[3,2],[3,4]])=2
19 | // @test(3,[[1,0],[2,0]])=0
20 | /**
21 | * @param {number} n
22 | * @param {number[][]} connections
23 | * @return {number}
24 | */
25 | var minReorder = function (n, connections) {
26 | }
27 | `,
28 | `// @algorithm
29 |
30 | //@test([1,2])
31 | /**
32 | * @param {TreeNode} root
33 | * @return {void}
34 | */
35 | var func = function(root) {
36 | }
37 | `,
38 | `// @algorithm
39 |
40 | // @test([4,2,7,1,3],2)=[2,1,3]
41 | /**
42 | * Definition for a binary tree node.
43 | * function TreeNode(val) {
44 | * this.val = val;
45 | * this.left = this.right = null;
46 | * }
47 | */
48 | /**
49 | * @param {TreeNode} root
50 | * @param {number} val
51 | * @return {TreeNode}
52 | */
53 | var searchBST = function (root, val) {
54 | };`,
55 | ]
56 | suite('Util Test Suite', () => {
57 | test('test getTestCaseList', () => {
58 | const testCaseList = getTestCaseList(cases[0])
59 | assert.equal(testCaseList.length, 1)
60 | const { testCase, funcName, paramsTypes, resultType } = testCaseList[0]
61 | assert.deepEqual(testCase, [
62 | '// @test(6,[[0,1],[1,3],[2,3],[4,0],[4,5]])=3',
63 | '// @test(5,[[1,0],[1,2],[3,2],[3,4]])=2',
64 | '// @test(3,[[1,0],[2,0]])=0',
65 | ])
66 | assert.equal(funcName, 'minReorder')
67 | assert.deepEqual(paramsTypes, ['number', 'number[][]'])
68 | assert.equal(resultType, 'number')
69 | })
70 | test('test void return', () => {
71 | const testCaseList = getTestCaseList(cases[1])
72 | const { paramsTypes, resultType } = testCaseList[0]
73 | assert.equal(testCaseList.length, 1)
74 | assert.equal(resultType, 'void')
75 | assert.deepEqual(paramsTypes, ['TreeNode'])
76 | })
77 | test('test ', () => {
78 | const testCaseList = getTestCaseList(cases[2])
79 | assert.equal(testCaseList.length, 1)
80 | // assert.equal(resultType, 'void')
81 | // assert.deepEqual(paramsTypes, ['TreeNode'])
82 | })
83 | test('test add comment', () => {
84 | const source = `
85 | //this is a test
86 | /**
87 | * this is a test too
88 | */
89 | function main() {
90 |
91 | }
92 | `
93 | const result = `
94 | //hhh
95 | //this is a test
96 | /**
97 | * this is a test too
98 | */
99 | function main() {
100 |
101 | }
102 | `
103 | const out = addComment(source, 'hhh', 'main')
104 | assert.deepStrictEqual(out, result)
105 | })
106 | test('test unionArr', () => {
107 | const list: UnionArrDemo[] = [
108 | {
109 | params: [
110 | ['xiao', 'li'],
111 | ['li', 'wang'],
112 | ],
113 | result: ['xiao', 'li', 'wang'],
114 | },
115 | {
116 | params: [['xiao', 'li'], []],
117 | result: ['xiao', 'li'],
118 | },
119 | {
120 | params: [[], []],
121 | result: [],
122 | },
123 | {
124 | params: [[], ['abc']],
125 | result: ['abc'],
126 | },
127 | ]
128 | list.forEach(({ params, result }) => assert.deepStrictEqual(unionArr(params[0], params[1]), result))
129 | })
130 | })
131 |
--------------------------------------------------------------------------------
/src/history/storage.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import { config } from '../config'
3 | import { pathExists, readJson, writeJson, ensureFile } from 'fs-extra'
4 | import { CheckResponse, Lang, UpdateCommentOption, UpdateRemoteCommentOption } from '../model/common'
5 | import { apiCn } from '../api'
6 | import { formatMemory, formatTimestamp } from '.'
7 | interface Param {
8 | filePath: string
9 | text: string
10 | result: CheckResponse
11 | desc?: string
12 | }
13 | interface LocalSubmit {
14 | id: string
15 | code: string
16 | submission: Submission
17 | result: CheckResponse
18 | }
19 | interface Submission {
20 | id: string
21 | statusDisplay: string
22 | lang: string
23 | runtime: string
24 | timestamp: string
25 | url: string
26 | isPending: string
27 | memory: string
28 | submissionComment: string
29 | }
30 | class SubmitStorage {
31 | static submitStorage = new SubmitStorage()
32 | arr: Param[] = []
33 | isWork = false
34 | getFilePath(question_id: string) {
35 | const cacheDir = path.join(config.cacheDir, 'submit')
36 | return path.join(cacheDir, question_id + '.json')
37 | }
38 | async read(question_id: string): Promise {
39 | const filePath = this.getFilePath(question_id)
40 | try {
41 | const arr = await readJson(filePath)
42 | return arr
43 | } catch (err) {
44 | return []
45 | }
46 | }
47 | async save(options: Param) {
48 | if (this.isWork) {
49 | this.arr.push(options)
50 | } else {
51 | this.isWork = true
52 | await this.innerSave(options)
53 | }
54 | }
55 | saveSubmit(options: Param) {
56 | if (options.desc) {
57 | this.updateRemoteComment({
58 | id: options.result.submission_id,
59 | comment: options.desc,
60 | })
61 | }
62 |
63 | this.save(options)
64 | }
65 | updateRemoteComment({ id, comment }: UpdateRemoteCommentOption) {
66 | if (config.lang === Lang.cn && comment) {
67 | return apiCn.api.updateComment({
68 | submissionId: id,
69 | comment: comment,
70 | })
71 | }
72 | }
73 | async updateComment({ id, questionId, comment }: UpdateCommentOption): Promise {
74 | const arr = await this.read(questionId)
75 | const item = arr.find((v) => v.id === id)
76 | if (item) {
77 | item.submission.submissionComment = comment
78 | }
79 | const filePath = this.getFilePath(questionId)
80 | return writeJson(filePath, arr)
81 | }
82 | private async innerSave(options: Param): Promise {
83 | try {
84 | const id = options.result.question_id as string
85 | const r = options.result
86 | const filePath = this.getFilePath(id)
87 | const obj: LocalSubmit = {
88 | id: r.submission_id,
89 | code: options.text,
90 | submission: {
91 | id: r.submission_id,
92 | isPending: 'Not Pending',
93 | submissionComment: options.desc || '',
94 | lang: r.lang,
95 | memory: formatMemory(r.memory),
96 | runtime: r.status_runtime,
97 | statusDisplay: r.status_msg,
98 | timestamp: formatTimestamp(r.task_finish_time),
99 | url: '/submissions/detail/' + r.submission_id + '/',
100 | },
101 | result: options.result,
102 | }
103 | let arr: LocalSubmit[] = []
104 | const exist = await pathExists(filePath)
105 | if (exist) {
106 | arr = await readJson(filePath)
107 | arr.push(obj)
108 | } else {
109 | arr.push(obj)
110 | }
111 | await ensureFile(filePath)
112 | await writeJson(filePath, arr)
113 | } catch (err) {
114 | console.log(err)
115 | }
116 | if (this.arr.length) {
117 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
118 | const opt = this.arr.shift()!
119 | await this.innerSave(opt)
120 | } else {
121 | this.isWork = false
122 | }
123 | }
124 | }
125 |
126 | export const submitStorage = SubmitStorage.submitStorage
127 |
--------------------------------------------------------------------------------
/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import { config } from '../config'
2 | import * as apiCn from './api.cn'
3 | import * as apiEn from './api'
4 | import { getDb } from '../db'
5 | import * as modelCn from '../model/question.cn'
6 | import * as modelEn from '../model/question'
7 |
8 | export const api = {
9 | async fetchQuestionDetailById(id: string | number) {
10 | if (typeof id === 'string') {
11 | id = parseInt(id)
12 | }
13 | const db = await getDb()
14 | const out = await db.read(id)
15 | if (out) {
16 | return JSON.parse(out)
17 | }
18 | },
19 | async fetchQuestionDetail(titleSlug: string) {
20 | if (config.lang === 'en') {
21 | return apiEn.api.fetchQuestionDetail(titleSlug)
22 | } else {
23 | return apiCn.api.fetchQuestionDetail(titleSlug)
24 | }
25 | },
26 | async fetchContestQuestionDetail(titleSlug: string, weekname: string) {
27 | if (config.lang === 'en') {
28 | return apiEn.api.fetchContestQuestionDetail(titleSlug, weekname)
29 | } else {
30 | return apiCn.api.fetchContestQuestionDetail(titleSlug, weekname)
31 | }
32 | },
33 | async fetchQuestionByItemId(id: string) {
34 | if (config.lang === 'en') {
35 | const res = await apiEn.api.fetchChapterItem(id)
36 | return res.item.question
37 | }
38 | },
39 | async saveQuestionDetail(question) {
40 | const db = await getDb()
41 | await db.add(JSON.stringify(question), Number(question.questionId), Number(question.questionFrontendId))
42 | },
43 | async submitContest(options: modelCn.SubmitContestOptions | modelEn.SubmitContestOptions) {
44 | if (config.lang === 'en') {
45 | return apiEn.api.submitContest(options)
46 | } else {
47 | return apiCn.api.submitContest(options)
48 | }
49 | },
50 | async submit(options: modelCn.SubmitOptions | modelEn.SubmitOptions) {
51 | if (config.lang === 'en') {
52 | return apiEn.api.submit(options)
53 | } else {
54 | return apiCn.api.submit(options)
55 | }
56 | },
57 |
58 | async check(options: modelCn.CheckOptions | modelEn.CheckOptions) {
59 | if (config.lang === 'en') {
60 | return apiEn.api.check(options)
61 | } else {
62 | return apiCn.api.check(options)
63 | }
64 | },
65 | checkContest(options: modelCn.CheckContestOptions | modelEn.CheckContestOptions) {
66 | if (config.lang === 'en') {
67 | return apiEn.api.checkContest(options)
68 | } else {
69 | return apiCn.api.checkContest(options)
70 | }
71 | },
72 | async getAllQuestions() {
73 | if (config.lang === 'en') {
74 | return apiEn.api.getAllQuestions()
75 | } else {
76 | return apiCn.api.getAllQuestions()
77 | }
78 | },
79 | async fetchSubmissions(options: modelCn.SubmissionsOptions | modelEn.SubmissionsOptions) {
80 | if (config.lang === 'en') {
81 | return apiEn.api.fetchSubmissions(options)
82 | } else {
83 | return apiCn.api.fetchSubmissions(options)
84 | }
85 | },
86 | async fetchSubmissionDetail(options: modelCn.SubmissionDetailOptions) {
87 | if (config.lang === 'en') {
88 | return apiEn.api.fetchSubmissionDetail(options)
89 | } else {
90 | return apiCn.api.fetchSubmissionDetail(options)
91 | }
92 | },
93 | }
94 |
95 | export async function refreshQuestions() {
96 | if (config.lang === 'en') {
97 | await apiEn.api.refreshQuestions()
98 | } else {
99 | await apiCn.api.refreshQuestions()
100 | }
101 | }
102 |
103 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
104 | function handleCategorieQuestions(data) {
105 | const { stat_status_pairs } = data
106 | const questions = stat_status_pairs.map((v) => ({
107 | fid: String(v.stat.frontend_question_id),
108 | level: v.difficulty.level,
109 | id: v.stat.question_id,
110 | title: v.stat.question__title,
111 | slug: v.stat.question__title_slug,
112 | acs: v.stat.total_acs,
113 | submitted: v.stat.total_submitted,
114 | paid_only: v.paid_only,
115 | status: v.status,
116 | name: v.stat.question__title,
117 | }))
118 | return questions
119 | }
120 | export { apiEn, apiCn }
121 |
--------------------------------------------------------------------------------
/src/history/answer.ts:
--------------------------------------------------------------------------------
1 | import { generateId, getDesc, getFuncNames, QuestionMeta, readFileAsync } from '../common/util'
2 | import { submitStorage } from './storage'
3 | import * as path from 'path'
4 | import { config } from '../config'
5 | import { fetchQuestion, getName } from '../webview/questionPreview'
6 | import { preprocessCode } from '../util'
7 | import { CodeLang, isAlgorithm, langMap } from '../common/langConfig'
8 | import { Service } from '../lang/common'
9 | import { writeFile } from '../common/util'
10 | import { readJson, writeJson, ensureFile } from 'fs-extra'
11 | import { UpdateCommentOption } from '../model/common'
12 | interface Answer {
13 | id: string
14 | code: string
15 | desc: string
16 | timestamp: string
17 | lang?: string
18 | titleSlug?: string
19 | questionId?: string
20 | }
21 |
22 | export class AnswerStorage {
23 | static answerStorage = new AnswerStorage()
24 | getFilePath(questionId: string) {
25 | const cacheDir = path.join(config.cacheDir, 'answer')
26 | return path.join(cacheDir, questionId + '.json')
27 | }
28 | static saveSubmit(filePath: string, text: string, result: any) {
29 | const desc = getDesc(text, filePath) || ''
30 |
31 | submitStorage.save({
32 | filePath: filePath,
33 | text,
34 | result,
35 | desc,
36 | })
37 | }
38 | async read(questionId: string): Promise {
39 | const filePath = this.getFilePath(questionId)
40 | try {
41 | const arr = await readJson(filePath)
42 | return arr
43 | } catch (err) {
44 | console.log(err)
45 | return []
46 | }
47 | }
48 | async save(data: string, questionMeta: QuestionMeta) {
49 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
50 | const id = questionMeta.id!
51 | const historyfilePath = this.getFilePath(id)
52 | const arr = await this.read(id)
53 | const newAnswer: Answer = {
54 | code: data,
55 | id: generateId(),
56 | desc: questionMeta.desc || '',
57 | timestamp: Math.floor(Date.now() / 1000).toString(),
58 | lang: questionMeta.lang,
59 | questionId: id,
60 | titleSlug: questionMeta.titleSlug,
61 | }
62 | arr.push(newAnswer)
63 | await ensureFile(historyfilePath)
64 | await writeJson(historyfilePath, arr)
65 | }
66 | async updateComment({ id, questionId, comment }: UpdateCommentOption) {
67 | const arr = await this.read(questionId)
68 | const historyfilePath = this.getFilePath(questionId)
69 | const item = arr.find((v) => v.id === id)
70 | if (item) {
71 | item.desc = comment
72 | }
73 | await writeJson(historyfilePath, arr)
74 | }
75 | async newAnswer(filePath: string) {
76 | const data = await readFileAsync(filePath, { encoding: 'utf8' })
77 | const { questionMeta } = getFuncNames(data, filePath)
78 | const id = questionMeta.id
79 | if (!id) {
80 | return
81 | }
82 | await this.save(data, questionMeta)
83 |
84 | const { weekname, lang: langSlug } = questionMeta
85 | const param = {
86 | titleSlug: questionMeta.titleSlug,
87 | weekname: questionMeta.weekname,
88 | questionId: questionMeta.id,
89 | }
90 | const question = await fetchQuestion(param)
91 | if (!question) {
92 | return
93 | }
94 | const { codeSnippets, questionFrontendId, title, translatedTitle } = question
95 | const codeSnippet = codeSnippets.find((codeSnippet) => codeSnippet.langSlug === langSlug)
96 | if (codeSnippet) {
97 | const langSlug = codeSnippet.langSlug
98 | const langConfig = langMap[langSlug]
99 | const { name } = getName(questionFrontendId, title, translatedTitle, codeSnippet)
100 | let code = preprocessCode(question, weekname, codeSnippet, name)
101 | if (langConfig.lang === CodeLang.Java) {
102 | code = code.replace('class Solution', 'public class Solution')
103 | }
104 | if (isAlgorithm(langConfig.lang)) {
105 | await Service.handlePreImport(filePath)
106 | }
107 |
108 | await writeFile(filePath, code)
109 | }
110 | }
111 | }
112 | export const answerStorage = AnswerStorage.answerStorage
113 |
--------------------------------------------------------------------------------
/template/golang/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/franela/goblin"
7 | )
8 |
9 | func Test(t *testing.T) {
10 | g := Goblin(t)
11 | g.Describe("test parse", func() {
12 | g.It("should parse integer", func() {
13 | a := parseInteger("12")
14 | g.Assert(a).Equal(12)
15 | })
16 | g.It("should parse string", func() {
17 | a := parseString("\"12\"")
18 | g.Assert(a).Equal("12")
19 | })
20 | g.It("should parse float", func() {
21 | a := parseFloat("1.22")
22 | g.Assert(a).Equal(1.22)
23 | })
24 | g.It("should parse integer[]", func() {
25 | a := parseIntegerArr("[1,2,3]")
26 | g.Assert(a).Equal([]int{1, 2, 3})
27 | })
28 | g.It("should parse string[]", func() {
29 | a := parseStringArr("[\"1\",\"2\",\"3\"]")
30 | g.Assert(a).Equal([]string{"1", "2", "3"})
31 | })
32 | g.It("should parse integer[][]", func() {
33 | a := parseIntegerArrArr("[[1,2,3],[4,5,6]]")
34 | g.Assert(a).Equal([][]int{[]int{1, 2, 3}, []int{4, 5, 6}})
35 | })
36 | g.It("should parse string[][]", func() {
37 | a := parseStringArrArr("[[\"1\",\"2\",\"3\"],[\"4\",\"5\",\"6\"]]")
38 | g.Assert(a).Equal([][]string{[]string{"1", "2", "3"}, []string{"4", "5", "6"}})
39 | })
40 | })
41 | g.Describe("test serialize", func() {
42 | g.It("should serialize integer", func() {
43 | a := serializeInterface(12)
44 | g.Assert(a).Equal("12")
45 | })
46 | g.It("should serialize string", func() {
47 | a := serializeInterface("12")
48 | g.Assert(a).Equal("\"12\"")
49 | })
50 | g.It("should serialize double", func() {
51 | a := serializeFloat(1.22)
52 | g.Assert(a).Equal("1.22000")
53 | })
54 | g.It("should serialize integer[]", func() {
55 | a := serializeInterface([]int{1, 2, 3})
56 | g.Assert(a).Equal("[1,2,3]")
57 | })
58 | g.It("should serialize string[]", func() {
59 | a := serializeInterface([]string{"1", "2", "3"})
60 | g.Assert(a).Equal("[\"1\",\"2\",\"3\"]")
61 | })
62 | g.It("should serialize integer[][]", func() {
63 | a := serializeInterface([][]int{[]int{1, 2, 3}, []int{4, 5, 6}})
64 | g.Assert(a).Equal("[[1,2,3],[4,5,6]]")
65 | })
66 | g.It("should serialize string[][]", func() {
67 | a := serializeInterface([][]string{[]string{"1", "2", "3"}, []string{"4", "5", "6"}})
68 | g.Assert(a).Equal("[[\"1\",\"2\",\"3\"],[\"4\",\"5\",\"6\"]]")
69 | })
70 | g.It("should serialize list", func() {
71 | a := serializeInterface([]int{1, 2, 3})
72 | g.Assert(a).Equal("[1,2,3]")
73 | })
74 |
75 | g.It("should serialize list", func() {
76 | a := serializeInterface([]string{"1", "2", "3"})
77 | g.Assert(a).Equal("[\"1\",\"2\",\"3\"]")
78 | })
79 | g.It("should serialize list>", func() {
80 | a := serializeInterface([][]int{[]int{1, 2, 3}, []int{4, 5, 6}})
81 | g.Assert(a).Equal("[[1,2,3],[4,5,6]]")
82 | })
83 | g.It("should serialize list>", func() {
84 | a := serializeInterface([][]string{[]string{"1", "2", "3"}, []string{"4", "5", "6"}})
85 | g.Assert(a).Equal("[[\"1\",\"2\",\"3\"],[\"4\",\"5\",\"6\"]]")
86 | })
87 | g.It("should serialize TreeNode", func() {
88 | treeNode := deserializeTreeNode("[1,2,3,null,null,4,5]")
89 | str := serializeTreeNode(treeNode)
90 | g.Assert(str).Equal("[1,2,3,null,null,4,5]")
91 |
92 | })
93 | g.It("should serialize ListNode", func() {
94 | treeNode := deserializeListNode("[1,2,3,4,5]")
95 | str := serializeListNode(treeNode)
96 | g.Assert(str).Equal("[1,2,3,4,5]")
97 | })
98 | g.It("should serialize list", func() {
99 | data := "[[1,2,3,null,null,4,5],[1,2,3]]"
100 | t1 := deserializeTreeNodeArr(data)
101 | str := serializeTreeNodeArr(t1)
102 | g.Assert(str).Equal(data)
103 | })
104 | g.It("should serialize list", func() {
105 | data := "[[4,5,6],[1,2,3]]"
106 | t1 := deserializeListNodeArr(data)
107 | str := serializeListNodeArr(t1)
108 | g.Assert(str).Equal(data)
109 | })
110 | })
111 | }
112 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { languages, commands } from 'vscode'
3 | import { CodelensProvider } from './provider/codelensProvider'
4 | import {
5 | testCodeCommand,
6 | buildCodeCommand,
7 | submitCommand,
8 | refreshCommand,
9 | switchEndpointCommand,
10 | signInCommand,
11 | switchCodeLangCommand,
12 | debugCodeCommand,
13 | getDescriptionCommand,
14 | searchCommand,
15 | memoFilePreviewCommand,
16 | addFolderCommand,
17 | addMemoFileCommand,
18 | removeMemoFileCommand,
19 | switchDataBaseCommand,
20 | viewSubmitHistoryCommand,
21 | newAnswerCommand,
22 | } from './commands'
23 | import { QuestionsProvider } from './provider/questionsProvider'
24 | import { createQuestionPanelCommand } from './webview/questionPreview'
25 | import { config, onChangeConfig } from './config'
26 | import { registerForSnippetProviders } from './provider/snippetProvider'
27 | import { registerDebug } from './debug/launch'
28 | import { registerCompletionItemProvider } from './provider/completionProvider'
29 | import { MemoProvider } from './provider/memoProvider'
30 | import { childProcessProxy } from './process'
31 | export function activate(context: vscode.ExtensionContext) {
32 | const subscriptions = context.subscriptions
33 | registerCompletionItemProvider(context)
34 | registerForSnippetProviders(context)
35 | registerDebug()
36 | const codelensProvider = new CodelensProvider()
37 | languages.registerCodeLensProvider('*', codelensProvider)
38 | const questionsProvider = new QuestionsProvider(vscode.workspace.workspaceFolders, context.extensionPath)
39 | config.questionsProvider = questionsProvider
40 | const memoProvider = new MemoProvider()
41 | subscriptions.push(commands.registerCommand('algorithm.testCode', testCodeCommand))
42 | subscriptions.push(commands.registerCommand('algorithm.debugCode', debugCodeCommand))
43 | subscriptions.push(commands.registerCommand('algorithm.buildCode', buildCodeCommand.bind(null, context)))
44 | subscriptions.push(commands.registerCommand('algorithm.submit', submitCommand.bind(null, questionsProvider)))
45 | subscriptions.push(
46 | commands.registerCommand('algorithm.getDescription', getDescriptionCommand.bind(null, context.extensionPath))
47 | )
48 | subscriptions.push(
49 | commands.registerCommand(
50 | 'algorithm.questionPreview',
51 | createQuestionPanelCommand.bind(null, context.extensionPath)
52 | )
53 | )
54 | subscriptions.push(
55 | commands.registerCommand('algorithm.refreshQuestions', refreshCommand.bind(null, questionsProvider))
56 | )
57 | subscriptions.push(
58 | commands.registerCommand('algorithm.switchEndpoint', switchEndpointCommand.bind(null, questionsProvider))
59 | )
60 | subscriptions.push(commands.registerCommand('algorithm.signIn', signInCommand.bind(null, questionsProvider)))
61 | subscriptions.push(
62 | commands.registerCommand('algorithm.switchCodeLang', switchCodeLangCommand.bind(null, questionsProvider))
63 | )
64 | subscriptions.push(commands.registerCommand('algorithm.search', searchCommand))
65 | subscriptions.push(commands.registerCommand('algorithm.memoFilePreview', memoFilePreviewCommand))
66 | subscriptions.push(commands.registerCommand('algorithm.addFolder', addFolderCommand.bind(null, memoProvider)))
67 | subscriptions.push(commands.registerCommand('algorithm.addMemoFile', addMemoFileCommand.bind(null, memoProvider)))
68 | subscriptions.push(
69 | commands.registerCommand('algorithm.removeMemoFile', removeMemoFileCommand.bind(null, memoProvider))
70 | )
71 | subscriptions.push(commands.registerCommand('algorithm.switchDataBase', switchDataBaseCommand))
72 | subscriptions.push(
73 | commands.registerCommand('algorithm.viewSubmitHistory', viewSubmitHistoryCommand.bind(null, context))
74 | )
75 | subscriptions.push(commands.registerCommand('algorithm.newAnswer', newAnswerCommand))
76 |
77 | vscode.window.createTreeView('questions', {
78 | treeDataProvider: questionsProvider,
79 | showCollapseAll: true,
80 | })
81 | vscode.window.createTreeView('memo', {
82 | treeDataProvider: memoProvider,
83 | showCollapseAll: true,
84 | })
85 | subscriptions.push(vscode.workspace.onDidChangeConfiguration(onChangeConfig.bind(null, questionsProvider)))
86 | }
87 |
88 | export function deactivate() {
89 | childProcessProxy.clear()
90 | }
--------------------------------------------------------------------------------
/src/provider/resolver.cn.ts:
--------------------------------------------------------------------------------
1 | import {
2 | api,
3 | getTags,
4 | getQuestionsByTag,
5 | getQuestionsByDifficult,
6 | getAllQuestions,
7 | getCategories,
8 | getQuestionsByCategory,
9 | } from '../api/api.cn'
10 | import { ResolverType } from './resolver'
11 | import { normalizeQuestions } from '../common/util'
12 |
13 | export const resolverCn: ResolverType = {
14 | Query() {
15 | //'Company', 'Favorite',
16 | return ['All', 'Difficulty', 'Category', 'Tag', 'Contest', 'TodayRecord'].map((v) => ({
17 | key: v.toLowerCase(),
18 | type: 'Catalogue',
19 | label: v,
20 | id: v,
21 | }))
22 | },
23 | Catalogue: {
24 | async all() {
25 | const questions = await getAllQuestions()
26 | return normalizeQuestions(questions, 'all')
27 | },
28 | difficulty() {
29 | return ['Easy', 'Medium', 'Hard'].map((v) => ({
30 | key: v.toLowerCase(),
31 | type: 'Difficulty',
32 | label: v,
33 | id: v,
34 | }))
35 | },
36 | category() {
37 | const categories = getCategories()
38 | return categories.map((v) => ({
39 | label: v.label,
40 | type: 'Category',
41 | key: v.category_slug,
42 | id: 'Category' + v.category_slug,
43 | }))
44 | },
45 | async tag() {
46 | const tags = await getTags()
47 | return tags.map((tag) => ({
48 | key: 'tagkey',
49 | type: 'Tag',
50 | label: tag,
51 | id: tag,
52 | param: {
53 | tag,
54 | },
55 | }))
56 | },
57 |
58 | // company() {
59 |
60 | // },
61 | // favorite() {
62 |
63 | // },
64 | async contest() {
65 | const contests = await api.fetchContests()
66 | return contests.map((contest) => ({
67 | key: 'contestKey',
68 | type: 'Contest',
69 | label: contest.title,
70 | id: contest.title,
71 | param: {
72 | titleSlug: contest.titleSlug,
73 | },
74 | }))
75 | },
76 | async todayrecord() {
77 | const record = await api.fetchTodayRecord()
78 | return record.map((v) => ({
79 | type: 'Question',
80 | label: v.question.questionFrontendId + '.' + v.question.translatedTitle,
81 | isAC: v.userStatus === 'FINISH', //NOT_START
82 | isLast: true,
83 | id: 'todayrecord' + v.question.questionFrontendId,
84 | param: {
85 | titleSlug: v.question.titleSlug,
86 | },
87 | }))
88 | },
89 | },
90 | Category: {
91 | async algorithm() {
92 | const key = 'algorithm'
93 | const questions = await getQuestionsByCategory(key)
94 | return normalizeQuestions(questions, key)
95 | },
96 | async lcci() {
97 | const key = 'lcci'
98 | const questions = await getQuestionsByCategory(key)
99 | return normalizeQuestions(questions, key)
100 | },
101 | async lcof() {
102 | const key = 'lcof'
103 | const questions = await getQuestionsByCategory(key)
104 | return normalizeQuestions(questions, key)
105 | },
106 | },
107 | Tag: {
108 | async tagkey({ tag }) {
109 | if (!tag) {
110 | return []
111 | }
112 | const questions = await getQuestionsByTag(tag)
113 | return normalizeQuestions(questions, 'tag' + tag)
114 | },
115 | },
116 | Difficulty: {
117 | async easy() {
118 | const key = 'easy'
119 | const questions = await getQuestionsByDifficult(key)
120 | return normalizeQuestions(questions, key)
121 | },
122 | async medium() {
123 | const key = 'medium'
124 | const questions = await getQuestionsByDifficult(key)
125 | return normalizeQuestions(questions, key)
126 | },
127 | async hard() {
128 | const key = 'hard'
129 | const questions = await getQuestionsByDifficult(key)
130 | return normalizeQuestions(questions, key)
131 | },
132 | },
133 | // Company() {
134 |
135 | // },
136 | // Favorite() {
137 |
138 | // },
139 | Contest: {
140 | async contestKey({ titleSlug }) {
141 | if (!titleSlug) {
142 | return []
143 | }
144 | const data = await api.fetchContest(titleSlug)
145 | const questions = data.questions
146 | return questions.map((question) => ({
147 | type: 'QuestionContest',
148 | label: question.title,
149 | id: 'QuestionContest' + question.id,
150 | isLast: true,
151 | param: {
152 | titleSlug: question.title_slug,
153 | weekname: titleSlug,
154 | },
155 | }))
156 | },
157 | },
158 | }
159 |
--------------------------------------------------------------------------------
/media/highlight.css:
--------------------------------------------------------------------------------
1 | /*
2 | https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs2015.css
3 | */
4 | /*
5 | * Visual Studio 2015 dark style
6 | * Author: Nicolas LLOBERA
7 | */
8 |
9 |
10 | .hljs-keyword,
11 | .hljs-literal,
12 | .hljs-symbol,
13 | .hljs-name {
14 | color: #569CD6;
15 | }
16 | .hljs-link {
17 | color: #569CD6;
18 | text-decoration: underline;
19 | }
20 |
21 | .hljs-built_in,
22 | .hljs-type {
23 | color: #4EC9B0;
24 | }
25 |
26 | .hljs-number,
27 | .hljs-class {
28 | color: #B8D7A3;
29 | }
30 |
31 | .hljs-string,
32 | .hljs-meta-string {
33 | color: #D69D85;
34 | }
35 |
36 | .hljs-regexp,
37 | .hljs-template-tag {
38 | color: #9A5334;
39 | }
40 |
41 | .hljs-subst,
42 | .hljs-function,
43 | .hljs-title,
44 | .hljs-params,
45 | .hljs-formula {
46 | color: #DCDCDC;
47 | }
48 |
49 | .hljs-comment,
50 | .hljs-quote {
51 | color: #57A64A;
52 | font-style: italic;
53 | }
54 |
55 | .hljs-doctag {
56 | color: #608B4E;
57 | }
58 |
59 | .hljs-meta,
60 | .hljs-meta-keyword,
61 | .hljs-tag {
62 | color: #9B9B9B;
63 | }
64 |
65 | .hljs-variable,
66 | .hljs-template-variable {
67 | color: #BD63C5;
68 | }
69 |
70 | .hljs-attr,
71 | .hljs-attribute,
72 | .hljs-builtin-name {
73 | color: #9CDCFE;
74 | }
75 |
76 | .hljs-section {
77 | color: gold;
78 | }
79 |
80 | .hljs-emphasis {
81 | font-style: italic;
82 | }
83 |
84 | .hljs-strong {
85 | font-weight: bold;
86 | }
87 |
88 | /*.hljs-code {
89 | font-family:'Monospace';
90 | }*/
91 |
92 | .hljs-bullet,
93 | .hljs-selector-tag,
94 | .hljs-selector-id,
95 | .hljs-selector-class,
96 | .hljs-selector-attr,
97 | .hljs-selector-pseudo {
98 | color: #D7BA7D;
99 | }
100 |
101 | .hljs-addition {
102 | background-color: var(--vscode-diffEditor-insertedTextBackground, rgba(155, 185, 85, 0.2));
103 | color: rgb(155, 185, 85);
104 | display: inline-block;
105 | width: 100%;
106 | }
107 |
108 | .hljs-deletion {
109 | background: var(--vscode-diffEditor-removedTextBackground, rgba(255, 0, 0, 0.2));
110 | color: rgb(255, 0, 0);
111 | display: inline-block;
112 | width: 100%;
113 | }
114 |
115 |
116 | /*
117 | From https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs.css
118 | */
119 | /*
120 |
121 | Visual Studio-like style based on original C# coloring by Jason Diamond
122 |
123 | */
124 |
125 | .vscode-light .hljs-function,
126 | .vscode-light .hljs-params,
127 | .vscode-light .hljs-number,
128 | .vscode-light .hljs-class {
129 | color: inherit;
130 | }
131 |
132 | .vscode-light .hljs-comment,
133 | .vscode-light .hljs-quote,
134 | .vscode-light .hljs-number,
135 | .vscode-light .hljs-class,
136 | .vscode-light .hljs-variable {
137 | color: #008000;
138 | }
139 |
140 | .vscode-light .hljs-keyword,
141 | .vscode-light .hljs-selector-tag,
142 | .vscode-light .hljs-name,
143 | .vscode-light .hljs-tag {
144 | color: #00f;
145 | }
146 |
147 | .vscode-light .hljs-built_in,
148 | .vscode-light .hljs-builtin-name {
149 | color: #007acc;
150 | }
151 |
152 | .vscode-light .hljs-string,
153 | .vscode-light .hljs-section,
154 | .vscode-light .hljs-attribute,
155 | .vscode-light .hljs-literal,
156 | .vscode-light .hljs-template-tag,
157 | .vscode-light .hljs-template-variable,
158 | .vscode-light .hljs-type {
159 | color: #a31515;
160 | }
161 |
162 | .vscode-light .hljs-selector-attr,
163 | .vscode-light .hljs-selector-pseudo,
164 | .vscode-light .hljs-meta,
165 | .vscode-light .hljs-meta-keyword {
166 | color: #2b91af;
167 | }
168 |
169 | .vscode-light .hljs-title,
170 | .vscode-light .hljs-doctag {
171 | color: #808080;
172 | }
173 |
174 | .vscode-light .hljs-attr {
175 | color: #f00;
176 | }
177 |
178 | .vscode-light .hljs-symbol,
179 | .vscode-light .hljs-bullet,
180 | .vscode-light .hljs-link {
181 | color: #00b0e8;
182 | }
183 |
184 |
185 | .vscode-light .hljs-emphasis {
186 | font-style: italic;
187 | }
188 |
189 | .vscode-light .hljs-strong {
190 | font-weight: bold;
191 | }
--------------------------------------------------------------------------------
/src/babelPlugin.ts:
--------------------------------------------------------------------------------
1 | import { types as t } from '@babel/core'
2 | const regexp = /^@get\((-?\d+|Infinity|-Infinity)\)$/
3 |
4 | function generatelogicalExpression(arr: t.Expression[]) {
5 | if (arr.length === 2) {
6 | return t.logicalExpression('&&', arr[0], arr[1])
7 | } else if (arr.length > 2) {
8 | return t.logicalExpression('&&', arr[0], generatelogicalExpression(arr.slice(1)))
9 | } else {
10 | return arr[0]
11 | }
12 | }
13 |
14 | function toBinaryExpression(node) {
15 | return t.binaryExpression('>=', node, t.numericLiteral(0))
16 | }
17 | type NumberProperty = t.Identifier | t.NumericLiteral | t.BinaryExpression
18 | type ComputeProperty = t.Identifier | t.BinaryExpression
19 | function isValidNumberProperty(property: t.Expression | t.Identifier | t.PrivateName): property is NumberProperty {
20 | return property.type === 'Identifier' || property.type === 'NumericLiteral' || property.type === 'BinaryExpression'
21 | }
22 | function isValidComputeProperty(property: NumberProperty): property is ComputeProperty {
23 | return property.type === 'Identifier' || property.type === 'BinaryExpression'
24 | }
25 | export function outBoundArrayPlugin() {
26 | return {
27 | visitor: {
28 | VariableDeclaration(path: babel.NodePath) {
29 | if (
30 | t.isVariableDeclaration(path.node) &&
31 | path.node.leadingComments &&
32 | path.node.leadingComments.find((v) => regexp.test(v.value))
33 | ) {
34 | const comment = path.node.leadingComments.find((v) => regexp.test(v.value)) as t.Comment
35 | const regexpResult = regexp.exec(comment.value) as RegExpExecArray
36 | const numStr = regexpResult[1]
37 | let numNode: t.Identifier | t.UnaryExpression | t.NumericLiteral
38 | if (numStr === 'Infinity') {
39 | numNode = t.identifier('Infinity')
40 | } else if (numStr === '-Infinity') {
41 | numNode = t.unaryExpression('-', t.identifier('Infinity'))
42 | } else {
43 | numNode = t.numericLiteral(parseInt(numStr))
44 | }
45 | const declaration = path.node.declarations[0]
46 | const id = declaration.id
47 | if (!t.isIdentifier(id)) {
48 | return
49 | }
50 | const name = id.name
51 | const bind = path.scope.bindings[name]
52 | const referencePaths = bind.referencePaths
53 | referencePaths.forEach((r) => {
54 | let nodes: ComputeProperty[] = []
55 | while (r.parentPath && r.parentPath.node.type === 'MemberExpression' && r.parentPath.node.computed) {
56 | const node = r.parentPath.node
57 | if (!isValidNumberProperty(node.property)) {
58 | return
59 | }
60 | if (isValidComputeProperty(node.property)) {
61 | nodes.push(node.property)
62 | }
63 |
64 | r = r.parentPath
65 | }
66 |
67 | if (nodes.length && !(r.key === 'left' && r.parentPath && r.parentPath.type === 'AssignmentExpression')) {
68 | nodes = nodes.map((node) => toBinaryExpression(node))
69 | r.replaceWith(
70 | t.conditionalExpression(
71 | generatelogicalExpression(nodes),
72 | r.node as t.MemberExpression,
73 | numNode
74 | )
75 | )
76 | }
77 | })
78 | }
79 | },
80 | },
81 | }
82 | }
83 |
84 | export function generateAddTestCommentPlugin(funcName: string, comment: string) {
85 | return function addTestCommentPlugin() {
86 | return {
87 | visitor: {
88 | VariableDeclaration(path: babel.NodePath) {
89 | const funcDeclaration = path.node.declarations.find((dec) => {
90 | return (
91 | dec.id.type === 'Identifier' &&
92 | dec.id.name === funcName &&
93 | dec.init?.type === 'FunctionExpression'
94 | )
95 | })
96 | if (funcDeclaration) {
97 | path.addComment('leading', comment, true)
98 | path.stop()
99 | }
100 | },
101 | FunctionDeclaration(path: babel.NodePath) {
102 | const name = path.node.id?.name
103 | if (name === funcName) {
104 | path.addComment('leading', comment, true)
105 | path.stop()
106 | }
107 | },
108 | },
109 | }
110 | }
111 | }
112 |
113 | export function removeExtraTypePlugin() {
114 | return {
115 | visitor: {
116 | ClassDeclaration(path: babel.NodePath) {
117 | const extraTypes = ['ListNode', 'TreeNode']
118 | if (path.node.id && extraTypes.includes(path.node.id.name)) {
119 | path.remove()
120 | }
121 | },
122 | },
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/debug/launch.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable indent */
2 | import { Breakpoint, debug, SourceBreakpoint } from 'vscode'
3 | import { config } from '../config'
4 | import * as vscode from 'vscode'
5 | import { WorkspaceFolder, DebugConfiguration, ProviderResult, CancellationToken } from 'vscode'
6 | import * as path from 'path'
7 | import { getDebugConfig } from '../util'
8 | import { writeFileSync } from '../common/util'
9 | // import { Map } from '../common/map'
10 | interface Map {
11 | has(
12 | this: MapWith,
13 | key: CheckedString
14 | ): this is MapWith
15 |
16 | has(this: Map, key: CheckedString): this is MapWith
17 | }
18 |
19 | interface MapWith extends Map {
20 | get(k: DefiniteKey): V
21 | get(k: K): V | undefined
22 | }
23 | export function registerDebug() {
24 | let breaks: Breakpoint[] = []
25 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
26 | class ConfigurationProvider implements vscode.DebugConfigurationProvider {
27 | provideDebugConfigurations(
28 | _folder: WorkspaceFolder | undefined,
29 | _token?: CancellationToken
30 | ): ProviderResult {
31 | const debugConfiguration = getDebugConfig()
32 | return [debugConfiguration]
33 | }
34 | /**
35 | * Massage a debug configuration just before a debug session is being launched,
36 | * e.g. add all missing attributes to the debug configuration.
37 | */
38 | resolveDebugConfigurationWithSubstitutedVariables(
39 | _folder: WorkspaceFolder | undefined,
40 | debugConfig: DebugConfiguration,
41 | _token?: CancellationToken
42 | ): ProviderResult {
43 | const { nodeBinPath } = config
44 | debugConfig.runtimeExecutable = nodeBinPath
45 | return debugConfig
46 | }
47 | }
48 | class TaskProvider implements vscode.TaskProvider {
49 | static TaskType = 'algorithm'
50 | provideTasks() {
51 | const { debugOptionsFilePath, nodeBinPath } = config
52 | const debugTaskFilePath = path.resolve(__dirname, '../debugTask', 'index.js')
53 | const taskName = 'build'
54 | const tasks = [
55 | new vscode.Task(
56 | {
57 | type: TaskProvider.TaskType,
58 | },
59 | vscode.TaskScope.Workspace,
60 | taskName,
61 | TaskProvider.TaskType,
62 | new vscode.ProcessExecution(nodeBinPath, [debugTaskFilePath, '${file}', debugOptionsFilePath])
63 | ),
64 | ]
65 | const param = serializeBreaks(breaks as SourceBreakpoint[])
66 | writeFileSync(debugOptionsFilePath, param, { encoding: 'utf8' })
67 | return tasks
68 | }
69 | resolveTask(task: vscode.Task, _token?: CancellationToken | undefined): ProviderResult {
70 | return task
71 | }
72 | }
73 |
74 | debug.onDidChangeBreakpoints((e) => {
75 | breaks = breaks.concat(e.added)
76 | const removePoints = e.removed
77 | const editPoints = e.changed
78 | breaks = breaks.filter((b) => !removePoints.find((v) => v.id === b.id))
79 | breaks = breaks.map((v) => editPoints.find((eb) => eb.id === v.id) || v)
80 | })
81 | vscode.tasks.registerTaskProvider('algorithm', new TaskProvider())
82 |
83 | // Automatically set configuration more better
84 | // debug.registerDebugConfigurationProvider('node', new ConfigurationProvider())
85 | }
86 |
87 | interface CustomBreakpoint {
88 | path: string
89 | lines: number[]
90 | }
91 | function serializeBreaks(breaks: SourceBreakpoint[]): string {
92 | const pathMap = new Map()
93 | breaks.forEach((b) => {
94 | const p = b.location.uri.fsPath
95 | const line = b.location.range.start.line
96 | if (!pathMap.has(p)) {
97 | pathMap.set(p, [line])
98 | } else {
99 | pathMap.get(p)!.push(line)
100 | }
101 | })
102 | const r: CustomBreakpoint[] = []
103 | for (const key of pathMap.keys()) {
104 | r.push({ path: key, lines: pathMap.get(key)! })
105 | }
106 | return JSON.stringify(r)
107 | }
108 | export function tranfromToCustomBreakpoint(breaks: SourceBreakpoint[]) {
109 | const pathMap = new Map()
110 | breaks.forEach((b) => {
111 | const p = b.location.uri.fsPath
112 | const line = b.location.range.start.line
113 |
114 | if (!pathMap.has(p)) {
115 | pathMap.set(p, [line])
116 | } else {
117 | pathMap.get(p)!.push(line)
118 | }
119 | })
120 | const r: CustomBreakpoint[] = []
121 | for (const key of pathMap.keys()) {
122 | r.push({ path: key, lines: pathMap.get(key)! })
123 | }
124 | return r
125 | }
126 |
--------------------------------------------------------------------------------
/src/child_process/execTestCode.ts:
--------------------------------------------------------------------------------
1 | import rollup = require('rollup')
2 | import resolve from '@rollup/plugin-node-resolve'
3 | import rollupBabelPlugin from '@rollup/plugin-babel'
4 | import { transformSync } from '@babel/core'
5 | import * as path from 'path'
6 | import * as fs from 'fs'
7 | import sourceMap = require('source-map')
8 | import { TestResult, handleArgsType } from '../common/util'
9 | import { outBoundArrayPlugin } from '../babelPlugin'
10 | import { Script } from 'vm'
11 | import { handleMsg } from '../common/util'
12 | import { CodeLang, getFileLang } from '../common/langConfig'
13 | import presetTs = require('@babel/preset-typescript')
14 | import { TestOptions } from '../common/lang'
15 | const virtual = require('@rollup/plugin-virtual')
16 |
17 | const defaultTimeout = 10000
18 | const supportCodeLang = [CodeLang.JavaScript, CodeLang.TypeScript]
19 | let options = ''
20 | process.stdin.on('data', (data) => {
21 | options += data
22 | })
23 | process.stdin.on('end', async () => {
24 | try {
25 | const msg = await execTestCase(JSON.parse(options.toString()))
26 | console.log(msg)
27 | } catch (err) {
28 | console.log(err)
29 | }
30 | })
31 | async function execTestCase(options: TestOptions) {
32 | const { caseList, filePath, metaData } = options
33 | const lang = getFileLang(filePath)
34 | if (!supportCodeLang.includes(lang)) {
35 | return `${lang} is currently not supported`
36 | }
37 | const output = await buildCode(filePath, lang)
38 | const code = output[0].code
39 | const list: TestResult[] = []
40 | for (const { args, result: expect } of caseList) {
41 | const originArgs = [...args]
42 |
43 | const finalCode = handleArgsType(metaData, code, args)
44 | const script = new Script(finalCode, {})
45 | try {
46 | const result = script.runInNewContext(
47 | {
48 | console,
49 | },
50 | {
51 | timeout: defaultTimeout,
52 | displayErrors: true,
53 | lineOffset: 0,
54 | }
55 | )
56 | list.push({
57 | args: originArgs.join(','),
58 | expect: expect,
59 | result: result,
60 | })
61 | } catch (err) {
62 | return handleErrPosition(err, output[0].map as rollup.SourceMap, originArgs)
63 | }
64 | }
65 | return handleMsg(list)
66 | }
67 |
68 | async function buildCode(filePath: string, lang: CodeLang) {
69 | if (lang === CodeLang.TypeScript) {
70 | return buildTsCode(filePath)
71 | }
72 | return buildJsCode(filePath)
73 | }
74 | async function buildJsCode(filePath: string) {
75 | const plugins = [
76 | resolve(),
77 | rollupBabelPlugin({
78 | babelHelpers: 'bundled',
79 | comments: false,
80 | plugins: [outBoundArrayPlugin],
81 | }),
82 | ]
83 | const bundle = await rollup.rollup({
84 | input: filePath,
85 | treeshake: false,
86 | plugins,
87 | })
88 | const { output } = await bundle.generate({
89 | sourcemap: true,
90 | sourcemapPathTransform: (r, s) => {
91 | return path.join(path.parse(s).dir, r)
92 | },
93 | })
94 | return output
95 | }
96 |
97 | async function buildTsCode(filePath: string) {
98 | const code = fs.readFileSync(filePath, { encoding: 'utf8' })
99 | const fileDir = path.parse(filePath).dir
100 | const entry = transformSync(code, {
101 | filename: filePath,
102 | comments: false,
103 | cwd: fileDir,
104 | presets: [[presetTs, { onlyRemoveTypeImports: true }]],
105 | plugins: [outBoundArrayPlugin],
106 | })
107 |
108 | const bundle = await rollup.rollup({
109 | input: 'entry',
110 | treeshake: false,
111 | plugins: [
112 | virtual({
113 | entry: entry?.code || "",
114 | }),
115 | resolve({ rootDir: fileDir, browser: false }),
116 | ],
117 | })
118 | const { output } = await bundle.generate({})
119 | return output
120 | }
121 |
122 | async function handleErrPosition(err: Error, map: sourceMap.RawSourceMap, args: string[]) {
123 | const consumer = await new sourceMap.SourceMapConsumer(map)
124 |
125 | const regexp = /evalmachine\.:(\d+):?(\d+)?/g
126 | let msg = `× @test(${args.join(',')})\n`
127 | const stack: string = err.stack as string
128 | msg += stack.replace(regexp, (_, line, column) => {
129 | line = parseInt(line)
130 | column = parseInt(column) || 0
131 | const originPosition = consumer.originalPositionFor({
132 | line: line,
133 | column: column,
134 | })
135 | if (originPosition.source) {
136 | if (column) {
137 | return originPosition.source + ':' + originPosition.line + ':' + originPosition.column
138 | } else {
139 | return originPosition.source + ':' + originPosition.line
140 | }
141 | }
142 | return _
143 | })
144 | consumer.destroy()
145 | return msg
146 | }
147 |
--------------------------------------------------------------------------------
/src/lang/javascript.ts:
--------------------------------------------------------------------------------
1 | import { CaseList, getFuncNames, readFileAsync } from '../common/util'
2 | import { config } from '../config'
3 | import * as vscode from 'vscode'
4 | import { BaseLang } from './base'
5 | import { execTestChildProcess } from '../execTestCode'
6 | import { TestOptions, LanguageMetaData, DebugOptions } from '../common/lang'
7 | import { main } from '../debugTask/index'
8 | import path = require('path')
9 | import { outBoundArrayPlugin } from '../babelPlugin'
10 | import babel = require('@babel/core')
11 | import rollup = require('rollup')
12 | import resolve from '@rollup/plugin-node-resolve'
13 | import rollupBabelPlugin from '@rollup/plugin-babel'
14 | import { window } from 'vscode'
15 | import { addComment } from '../common/transformCode'
16 | const virtual = require('@rollup/plugin-virtual')
17 | export class JavascriptParse extends BaseLang {
18 | static getPreImport() {
19 | return ''
20 | }
21 | funcRegExp = /^(?:(\s*function)|(.*=\s*function))/
22 | testRegExp = /\/\/\s*@test\(((?:"(?:\\.|[^"])*"|[^)])*)\)/
23 |
24 | async runMultiple(caseList: CaseList, originCode: string, _funcName: string) {
25 | const metaData = (await this.getQuestionMeta()) as LanguageMetaData | undefined
26 | if (!metaData) {
27 | throw new Error('question meta not found')
28 | }
29 | const options: TestOptions = {
30 | caseList,
31 | originCode,
32 | filePath: this.filePath,
33 | metaData: metaData,
34 | }
35 | return await execTestChildProcess(options)
36 | }
37 |
38 | shouldRemoveInBuild(_line: string): boolean {
39 | return false
40 | }
41 |
42 | async runInNewContext(_args: string[], _originCode: string, _funcName: string) {
43 | return ''
44 | }
45 | async handlePreImport() {
46 | return
47 | }
48 | async buildCode() {
49 | try {
50 | const filePath = this.filePath
51 | const text = this.text!
52 | const dir = path.parse(filePath).dir
53 | const { funcNames, questionMeta } = getFuncNames(text, filePath)
54 | const funcRunStr = 'console.log(' + funcNames.map((f) => f + '()').join('+') + ')'
55 | // The rollup will not transform code in virtual entry
56 | const entry: babel.BabelFileResult | null = await babel.transformAsync(text, {
57 | comments: false,
58 | compact: false,
59 | plugins: [outBoundArrayPlugin],
60 | })
61 | const entryCode = entry?.code + `\n${funcRunStr}`
62 | const bundle = await rollup.rollup({
63 | input: 'entry',
64 |
65 | treeshake: true,
66 | plugins: [
67 | // It use virtual entry because treeshake will remove unuse code.
68 | virtual({
69 | entry: entryCode,
70 | }),
71 | resolve({ rootDir: dir }),
72 | rollupBabelPlugin({
73 | babelHelpers: 'bundled',
74 | comments: false,
75 | shouldPrintComment: () => false,
76 | }),
77 | ],
78 | })
79 | const { output } = await bundle.generate({})
80 | let code = output[0].code
81 | code = code.replace(funcRunStr, '').replace(/;\s*$/, '')
82 | return {
83 | code,
84 | questionMeta,
85 | }
86 | } catch (err) {
87 | console.log('err:', err)
88 | window.showInformationMessage(`parse params err: ${err}`)
89 | return {
90 | code: '',
91 | questionMeta: {},
92 | }
93 | }
94 | }
95 | // do some thing before debug,eg. get testcase
96 | async beforeDebug(breaks: vscode.SourceBreakpoint[]) {
97 | const args = await this.resolveArgsFromBreaks(breaks)
98 | const metaData = (await this.getQuestionMeta()) as LanguageMetaData | undefined
99 | if (!metaData) {
100 | throw new Error('question meta not found')
101 | }
102 | const originCode = await readFileAsync(this.filePath, {
103 | encoding: 'utf8',
104 | })
105 | const options: DebugOptions = {
106 | originCode,
107 | filePath: this.filePath,
108 | metaData: metaData,
109 | }
110 | await this.writeTestCase(options, args)
111 | }
112 | async writeTestCase(options: DebugOptions, args: string[]) {
113 | return main(options, args)
114 | }
115 | getDebugConfig() {
116 | const { nodeBinPath } = config
117 |
118 | return {
119 | type: 'node',
120 | request: 'launch',
121 | name: 'debug question',
122 | skipFiles: ['/**'],
123 | program: '${workspaceFolder}/out/code.js',
124 | outFiles: ['${workspaceFolder}/out/*.js'],
125 | runtimeVersion: 'default',
126 | runtimeExecutable: nodeBinPath,
127 | sourceMaps: true,
128 | args: ['${file}'],
129 | // "preLaunchTask": "algorithm: build"
130 | }
131 | }
132 | public addComment(text: string, comment: string, funcName: string) {
133 | return addComment(text, comment, funcName)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/history/index.ts:
--------------------------------------------------------------------------------
1 | import { answerStorage } from './answer'
2 | import { submitStorage } from './storage'
3 | import { config } from '../config'
4 | import { HistoryType, Lang, UpdateCommentOption } from '../model/common'
5 | import { api } from '../api/index'
6 |
7 | export async function getHistory(questionId: string, fn: (code: string, lang: string) => string) {
8 | const originAnswers = await answerStorage.read(questionId)
9 | const formatAnswers = originAnswers
10 | .map((v) => {
11 | return {
12 | code: fn(v.code, v.lang || ''),
13 | obj: {
14 | desc: v.desc,
15 | timestamp: formatTimestamp(v.timestamp),
16 | id: v.id,
17 | lang: v.lang,
18 | },
19 | }
20 | })
21 | .reverse()
22 | const answerData = {
23 | header: [
24 | {
25 | label: 'description',
26 | key: 'desc',
27 | },
28 | {
29 | label: 'lang',
30 | key: 'lang',
31 | },
32 | {
33 | label: 'timestamp',
34 | key: 'timestamp',
35 | },
36 | ],
37 | arr: formatAnswers,
38 | }
39 | const originSubmitStorage = await submitStorage.read(questionId)
40 | const formatSubmits = originSubmitStorage
41 | .map((v) => {
42 | return {
43 | code: fn(v.code, v.submission.lang),
44 | obj: {
45 | ...v.submission,
46 | memory: v.submission.memory,
47 | comment: v.submission.submissionComment,
48 | statusDisplay: v.submission.statusDisplay,
49 | },
50 | }
51 | })
52 | .reverse()
53 |
54 | const submitStorageData = {
55 | header: [
56 | {
57 | label: 'statusDisplay',
58 | key: 'statusDisplay',
59 | },
60 | {
61 | label: 'lang',
62 | key: 'lang',
63 | },
64 | {
65 | label: 'memory',
66 | key: 'memory',
67 | },
68 | {
69 | label: 'runtime',
70 | key: 'runtime',
71 | },
72 | {
73 | label: 'comment',
74 | key: 'comment',
75 | },
76 | ],
77 | arr: formatSubmits,
78 | }
79 |
80 | return {
81 | id: questionId,
82 | answerData: answerData,
83 | localSubmit: submitStorageData,
84 | // remoteSubmit: remoteStorageData
85 | }
86 | }
87 | export async function getRemoteSubmits(questionId: string) {
88 | const question = await api.fetchQuestionDetailById(questionId)
89 | const res = await api.fetchSubmissions({ titleSlug: question.titleSlug })
90 | const submissions = res.submissionList.submissions || []
91 | const formatRemoteSubmits = submissions.map((v) => {
92 | return {
93 | code: '',
94 | obj: {
95 | ...v,
96 | memory: v.memory,
97 | timestamp: formatTimestamp(v.timestamp),
98 | comment: v?.submissionComment?.comment,
99 | id: v.id,
100 | },
101 | }
102 | })
103 | const remoteStorageData = {
104 | header: [
105 | {
106 | label: 'statusDisplay',
107 | key: 'statusDisplay',
108 | },
109 | {
110 | label: 'lang',
111 | key: 'lang',
112 | },
113 | {
114 | label: 'memory',
115 | key: 'memory',
116 | },
117 | {
118 | label: 'runtime',
119 | key: 'runtime',
120 | },
121 | {
122 | label: 'comment',
123 | key: 'comment',
124 | },
125 | ],
126 | arr: formatRemoteSubmits,
127 | }
128 | return remoteStorageData
129 | }
130 | export function formatTimestamp(time: string | number) {
131 | if (typeof time === 'string') {
132 | time = parseInt(time)
133 | }
134 | const num = time * 1000
135 | const date = new Date(num)
136 | const year = date.getFullYear()
137 | const month = (date.getMonth() + 1).toString().padStart(2, '0')
138 | const day = date.getDate().toString().padStart(2, '0')
139 | const hours = date.getHours().toString().padStart(2, '0')
140 | const minutes = date.getMinutes().toString().padStart(2, '0')
141 | if (config.lang === Lang.cn) {
142 | return `${year}/${month}/${day} ${hours}:${minutes}`
143 | } else {
144 | return `${year}/${month}/${day} ${hours}:${minutes}`
145 | }
146 | }
147 | export function formatMemory(memory: number) {
148 | return Math.floor(memory / (1024 * 1024)) + 'M'
149 | }
150 |
151 | // export function updateComment(type: HistoryType.Answer | HistoryType.LocalSubmit, options: UpdateCommentOption): Promise
152 | // export function updateComment(type: HistoryType.RemoteSubmit, options: UpdateRemoteCommentOption): Promise
153 |
154 | export async function updateComment(type: HistoryType, options: UpdateCommentOption): Promise {
155 | switch (type) {
156 | case HistoryType.Answer: {
157 | return answerStorage.updateComment(options)
158 | }
159 | case HistoryType.LocalSubmit: {
160 | return submitStorage.updateComment(options)
161 | }
162 | case HistoryType.RemoteSubmit: {
163 | return submitStorage.updateRemoteComment(options)
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/provider/resolver.ts:
--------------------------------------------------------------------------------
1 | import { api, getTags, getQuestionsByTag, getQuestionsByDifficult, getAllQuestions } from '../api/api'
2 | import { DailyWeekMap } from '../model/question'
3 | import { QuestionTree } from './questionsProvider'
4 | import { normalizeQuestions } from '../common/util'
5 |
6 | export interface ResolverParam {
7 | tag: string
8 | titleSlug: string
9 | weekname: string
10 | questionId: number
11 | chapterId: string
12 | itemId: string
13 | }
14 |
15 | interface ResolverReturn extends Pick {
16 | isLast?: boolean
17 | isAC?: boolean
18 | paidOnly?: boolean
19 | }
20 | export type ResolverFn = (param: Partial) => Promise | ResolverReturn[]
21 |
22 | interface RootResolver {
23 | Query: () => Promise | ResolverReturn[]
24 | }
25 | export interface ResolverTypeBase {
26 | [key: string]: ResolverFn | ResolverTypeBase
27 | }
28 | export type ResolverType = ResolverTypeBase & RootResolver
29 |
30 | export const resolverEn: ResolverType = {
31 | Query() {
32 | //'Company', 'Favorite',
33 | return ['All', 'Difficulty', 'Tag', 'Contest', 'DailyChallenge'].map((v) => ({
34 | type: 'Catalogue',
35 | key: v.toLowerCase(),
36 | label: v,
37 | id: v,
38 | }))
39 | },
40 | Catalogue: {
41 | async all() {
42 | const questions = await getAllQuestions()
43 | return normalizeQuestions(questions, 'all')
44 | },
45 | difficulty() {
46 | return ['Easy', 'Medium', 'Hard'].map((v) => ({
47 | type: 'Difficulty',
48 | key: v.toLowerCase(),
49 | label: v,
50 | id: v,
51 | }))
52 | },
53 | async tag() {
54 | const tags = await getTags()
55 | return tags.map((tag) => ({
56 | type: 'Tag',
57 | key: 'tagkey',
58 | label: tag,
59 | id: tag,
60 | param: {
61 | tag,
62 | },
63 | }))
64 | },
65 |
66 | // company() {
67 |
68 | // },
69 | // favorite() {
70 |
71 | // },
72 | async contest() {
73 | const contests = await api.fetchContests()
74 | return contests.map((contest) => ({
75 | type: 'Contest',
76 | key: 'contestKey',
77 | label: contest.title,
78 | id: contest.title,
79 | param: {
80 | titleSlug: contest.titleSlug,
81 | },
82 | }))
83 | },
84 | async dailychallenge() {
85 | const { chapters } = await api.fetchChapters()
86 | return chapters.map((chapter) => ({
87 | type: 'Chapter',
88 | label: chapter.title,
89 | id: chapter.title,
90 | param: {
91 | // titleSlug: chapter.slug,
92 | chapterId: chapter.id,
93 | },
94 | }))
95 | },
96 | },
97 | async Chapter({ chapterId }) {
98 | if (!chapterId) {
99 | return []
100 | }
101 | const chapterDetail = await api.fetchChapter(chapterId)
102 | const chaptersProgressRes = await api.fetchChapterProgress()
103 | const progress = chaptersProgressRes?.getOrCreateExploreSession?.progress
104 | let progressMap: DailyWeekMap | null = null
105 | try {
106 | progressMap = JSON.parse(progress)
107 | } catch (err) {
108 | progressMap = null
109 | }
110 |
111 | const items = chapterDetail.chapter.items
112 | return items.map((item) => {
113 | return {
114 | type: 'DailyQuestion',
115 | label: item.title,
116 | id: 'DailyQuestion' + item.title,
117 | isLast: true,
118 | isAC: progressMap ? progressMap[chapterId][item.id].is_complete : false,
119 | paidOnly: item.paidOnly,
120 | param: {
121 | itemId: item.id,
122 | },
123 | }
124 | })
125 | },
126 | Tag: {
127 | async tagkey({ tag }) {
128 | if (!tag) {
129 | return []
130 | }
131 | const questions = await getQuestionsByTag(tag)
132 | return normalizeQuestions(questions, 'tag' + tag)
133 | },
134 | },
135 |
136 | Difficulty: {
137 | async easy() {
138 | const key = 'easy'
139 | const questions = await getQuestionsByDifficult(key)
140 | return normalizeQuestions(questions, key)
141 | },
142 | async medium() {
143 | const key = 'medium'
144 | const questions = await getQuestionsByDifficult(key)
145 | return normalizeQuestions(questions, key)
146 | },
147 | async hard() {
148 | const key = 'hard'
149 | const questions = await getQuestionsByDifficult(key)
150 | return normalizeQuestions(questions, key)
151 | },
152 | },
153 | // Company() {
154 |
155 | // },
156 | // Favorite() {
157 |
158 | // },
159 | Contest: {
160 | async contestKey({ titleSlug }) {
161 | if (!titleSlug) {
162 | return []
163 | }
164 | const data = await api.fetchContest(titleSlug)
165 | const questions = data.questions
166 | return questions.map((question) => ({
167 | type: 'QuestionContest',
168 | label: question.id + ' ' + question.title,
169 | id: 'QuestionContest' + question.id,
170 | isLast: true,
171 | param: {
172 | titleSlug: question.title_slug,
173 | fatherTitleSlug: titleSlug,
174 | },
175 | }))
176 | },
177 | },
178 | }
179 |
--------------------------------------------------------------------------------
/src/lang/typescript.ts:
--------------------------------------------------------------------------------
1 | import { CaseList, getFuncNames, readFileAsync } from '../common/util'
2 | import { config } from '../config'
3 | import * as vscode from 'vscode'
4 | import { BaseLang } from './base'
5 | import { execTestChildProcess } from '../execTestCode'
6 | import { TestOptions, LanguageMetaData, DebugOptions } from '../common/lang'
7 | import { main } from '../debugTask/index'
8 | import * as path from 'path'
9 | import babel = require('@babel/core')
10 | import presetTs = require('@babel/preset-typescript')
11 | import { outBoundArrayPlugin } from '../babelPlugin'
12 | import rollup = require('rollup')
13 | import resolve from '@rollup/plugin-node-resolve'
14 | import rollupBabelPlugin from '@rollup/plugin-babel'
15 | import { window } from 'vscode'
16 | import { addComment } from '../common/transformCode'
17 | const virtual = require('@rollup/plugin-virtual')
18 | export class TypescriptParse extends BaseLang {
19 | static getPreImport() {
20 | return ''
21 | }
22 | funcRegExp = /^(?:(\s*function)|(.*=\s*function))/
23 | testRegExp = /\/\/\s*@test\(((?:"(?:\\.|[^"])*"|[^)])*)\)/
24 |
25 | async runMultiple(caseList: CaseList, originCode: string, _funcName: string) {
26 | const metaData = (await this.getQuestionMeta()) as LanguageMetaData | undefined
27 | if (!metaData) {
28 | throw new Error('question meta not found')
29 | }
30 | const options: TestOptions = {
31 | caseList,
32 | originCode,
33 | filePath: this.filePath,
34 | metaData: metaData,
35 | }
36 | return await execTestChildProcess(options)
37 | }
38 | async buildCode() {
39 | try {
40 | const filePath = this.filePath
41 | let text = this.text!
42 | text = text
43 | .split('\n')
44 | .filter((line) => !this.shouldRemoveInBuild(line))
45 | .join('\n')
46 |
47 | const dir = path.parse(filePath).dir
48 | const { funcNames, questionMeta } = getFuncNames(text, filePath)
49 | const funcRunStr = 'console.log(' + funcNames.map((f) => f + '()').join('+') + ')'
50 | // The rollup will not transform code in virtual entry
51 | const entry: babel.BabelFileResult | null = await babel.transformAsync(text, {
52 | filename: filePath,
53 | comments: false,
54 | compact: false,
55 | presets: [presetTs],
56 | plugins: [outBoundArrayPlugin],
57 | })
58 | const entryCode = entry?.code + `\n${funcRunStr}`
59 | const bundle = await rollup.rollup({
60 | input: 'entry',
61 |
62 | treeshake: true,
63 | plugins: [
64 | // It use virtual entry because treeshake will remove unuse code.
65 | virtual({
66 | entry: entryCode,
67 | }),
68 |
69 | resolve({ rootDir: dir, modulesOnly: true }),
70 |
71 | rollupBabelPlugin({
72 | babelHelpers: 'bundled',
73 | comments: false,
74 | shouldPrintComment: () => false,
75 | }),
76 | ],
77 | })
78 | const { output } = await bundle.generate({})
79 | let code = output[0].code
80 | code = code.replace(funcRunStr, '').replace(/;\s*$/, '')
81 | return {
82 | code,
83 | questionMeta,
84 | }
85 | } catch (err) {
86 | console.log('err:', err)
87 | window.showInformationMessage(`parse params err: ${err}`)
88 | return {
89 | code: '',
90 | questionMeta: {},
91 | }
92 | }
93 | }
94 | shouldRemoveInBuild(line: string): boolean {
95 | return /import\s*{\s*(ListNode|TreeNode)\s*}\s*from\s*'algm'/.test(line.trimLeft())
96 | }
97 |
98 | async runInNewContext(_args: string[], _originCode: string, _funcName: string) {
99 | return ''
100 | }
101 | async handlePreImport() {
102 | return
103 | }
104 |
105 | // do some thing before debug,eg. get testcase
106 | async beforeDebug(breaks: vscode.SourceBreakpoint[]) {
107 | const args = await this.resolveArgsFromBreaks(breaks)
108 | const metaData = (await this.getQuestionMeta()) as LanguageMetaData | undefined
109 | if (!metaData) {
110 | throw new Error('question meta not found')
111 | }
112 | const originCode = await readFileAsync(this.filePath, {
113 | encoding: 'utf8',
114 | })
115 | const options: DebugOptions = {
116 | originCode,
117 | filePath: this.filePath,
118 | metaData: metaData,
119 | }
120 | await this.writeTestCase(options, args)
121 | }
122 | async writeTestCase(options: DebugOptions, args: string[]) {
123 | return main(options, args)
124 | }
125 |
126 | getDebugConfig() {
127 | const { nodeBinPath } = config
128 |
129 | return {
130 | type: 'node',
131 | request: 'launch',
132 | name: 'debug question',
133 | skipFiles: ['/**'],
134 | program: '${workspaceFolder}/out/code.js',
135 | outFiles: ['${workspaceFolder}/out/*.js'],
136 | runtimeVersion: 'default',
137 | runtimeExecutable: nodeBinPath,
138 | sourceMaps: true,
139 | args: ['${file}'],
140 | // "preLaunchTask": "algorithm: build"
141 | }
142 | }
143 | public addComment(text: string, comment: string, funcName: string) {
144 | return addComment(text, comment, funcName)
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/docs/README_zh-CN.md:
--------------------------------------------------------------------------------
1 | # algorithm
2 |
3 | An excellent vscode extension for leetcode.
4 |
5 | ## Quick Start
6 |
7 | 
8 |
9 | ## Support
10 |
11 | - javascript
12 | - typescript
13 | - python3
14 | - golang
15 | - java
16 | - c++
17 | - shell
18 | - sql
19 |
20 | **将会很快支持其它语言**
21 |
22 | ## Main Features
23 |
24 | - 代码里包含默认测试用例,也可以添加自定义的测试用例,点击测试按钮 自动在本地运行,支持链表和二叉树。
25 |
26 | - debug 指定测试用例
27 |
28 | - 备忘录
29 |
30 | - 一题多解和提交历史
31 |
32 | - 支持 import module, 打包代码, 快速复制打包的代码.(此功能仅支持 js/ts)
33 |
34 | - 包含周赛 (正式比赛时直接使用 submit 是无效的,应该使用复制代码,然后在浏览器提交.)
35 |
36 | ## Other Features
37 |
38 | - 搜索题目
39 | - 每日一题
40 | - 支持同时登录 https://leetcode.com/ 和 https://leetcode.cn/
41 |
42 | ## 运行条件
43 |
44 | - javascript/typescript
45 |
46 | - Nodejs 12+
47 |
48 | > 默认会使用 node 执行测试和 debug,如果是使用 nvm 安装的,需要设置选项`algorithm.nodePath`
49 |
50 | - python3
51 |
52 | - python3
53 | - 安装官方 Python 插件(https://code.visualstudio.com/docs/python/python-tutorial)
54 |
55 | - golang
56 |
57 | - 确保 `go` 在环境变量里
58 | - 安装 golang 官方插件
59 |
60 | - java
61 |
62 | - 确保`java`,`javac`在环境变量里,或者[设置 javaPath 和 javacPath](#Setting).
63 | - 安装 java 官方插件(https://code.visualstudio.com/docs/java/java-tutorial)
64 |
65 | - c++
66 |
67 | - 安装 GCC C++编译器(g++).确保`g++`在环境变量里.
68 | - 安装 c++插件.(https://code.visualstudio.com/docs/cpp/config-linux)
69 |
70 | ## 选择题目
71 |
72 | 在 `algorithm view` 选择题目
73 |
74 | ## 切换语言
75 |
76 | 按 `ctrl+shift+p` 选择 `algorithm:switch default language`,目前支持 javascript,typescript,python3,golang,java,c++
77 |
78 | ## 切换数据库
79 |
80 | 按 `ctrl+shift+p` 选择 `algorithm:switch default database`,选择 `MySQL`,`MS SQL Server`,`Oracle`
81 |
82 | ## 快捷按钮
83 |
84 | > `view toolbar`有以下几种按钮
85 |
86 | - refresh question
87 | - search question
88 | - login in
89 | - switch endpoint
90 | - collapse all
91 |
92 | 
93 |
94 | ## 切换 leetcode 版本
95 |
96 | ### 目前支持:
97 |
98 | - leetcode.com
99 | - leetcode.cn
100 |
101 | > Note: 账户不互通.
102 |
103 | ## 登录
104 |
105 | 登录方式参考了官方插件 [vscode-leetcode](https://github.com/LeetCode-OpenSource/vscode-leetcode).
106 |
107 | - leetcode.com 支持 github 和 cookie.
108 | - leetcode.cn 支持 账号密码,github 和 cookie.
109 |
110 | ## 运行测试
111 |
112 | 代码里包含默认测试用例,也可以自定义添加测试用例,点击测试按钮 自动在本地运行。
113 |
114 | 
115 |
116 | ## Debug
117 |
118 | 只需要在一个测试用例前设置断点,然后给函数添加断点,点击 debug 按钮,便可以运行 debug。
119 | 支持 javascript,typescript,python3,golang,java。
120 |
121 | :tada:
122 |
123 | 
124 |
125 | > Note: 如果你正在调试 java,确保已经打开的问题没有语法错误,否则将会编译失败,因为所有问题在同一个目录下,而 java 相关插件只支持一个目录一个项目。比如你打开了问题 1,但是没有补全代码,然后开始 debug 问题 2,这时候会报语法错误,因为问题 1 应该返回一个值。如果语法正确后编译仍然失败尝试按住 `ctrl+shift+p` ,然后执行命令 `Java:Clean Java Language Server Workspace`.
126 |
127 | > Note:如果你在 mac m1 上调试 c++, 需要安装 vscode 插件 `CodeLLDB`
128 |
129 | ## 提交
130 |
131 | 点击 submit 提交代码. 你可以使用 build 查看最终提交的代码.
132 |
133 | 
134 |
135 | ## 备忘录
136 |
137 | 可以在备忘录里新建不同文件夹,例如 dfs,bfs 等,然后将题目添加进去
138 | 
139 |
140 | ## 一题多解和提交历史
141 |
142 | 
143 |
144 | 1. 点击 history,右边将会出现多种解法列表和提交历史。
145 | 2. 在代码里添加注释 `@desc $(method name)`,在保存多解和提交的时候会自动生成备注。
146 | 3. 右击鼠标可以看到命令`new leetcode answer`。这个命令将会保存解答,同时初始化新的解答。保存的解答可以在 4 中查看。
147 | 4. 保存的多种解答
148 | 5. 保存在本地的提交
149 | 6. 保存在 leetcode 的提交
150 |
151 | ### **注意!**
152 |
153 | 正式比赛时直接使用 submit 是无效的,应该先点击 build,再点击 copy, 最后在浏览器提交.
154 |
155 | ### build code & copy code
156 |
157 | - 点击 build
158 |
159 | 
160 |
161 | - 点击 copy
162 |
163 | 
164 |
165 | > 如果使用 js/ts 的话, 会自动导入包 [algm](https://github.com/supperchong/algm) ,包含了许多方便的函数和数据结构, 如 优先队列,线段树, 并查集 ,跳跃表等等. :rocket: 由于 treeshake , 打包后的代码不包含多余的代码.
166 |
167 | ## Setting
168 |
169 | 按 `ctrl+,` 或者打开 `file->Preferences->Settings`。
170 | 选择 `Extensions->algorithm`,将会看到:
171 |
172 | | Setting Name | Description | Default Value |
173 | | --------------- | ------------------------------------------------ | ------------- |
174 | | Auto Import Str | 字符串的值将会插入到代码前面 | "" |
175 | | Base Dir | 题目保存目录 | $HOME/.alg |
176 | | Code Lang | 使用的语言 | JavaScript |
177 | | Lang | 使用指定的终端 | leetcode.com |
178 | | Node Path | nodejs 可执行文件的路径. eg: /usr/local/bin/node | node |
179 | | javaPath | java 可执行文件的路径. | java |
180 | | javacPath | javac 可执行文件的路径. | javac |
181 | | Database | 执行 sql 所用的数据库 | MySQL |
182 | | displayLock | 展示锁住的题目 | false |
183 |
184 | ## 感谢
185 |
186 | 感谢官方插件[vscode-leetcode](https://github.com/LeetCode-OpenSource/vscode-leetcode), 本插件参考了登录方式和一些设计
187 |
--------------------------------------------------------------------------------
/src/provider/codelensProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { detectEnableExt, TestCaseParam } from '../common/util'
3 | import { getFileLang, getFileLangSlug, isAlgorithm, isSupportFile } from '../common/langConfig'
4 | import { Service } from '../lang/common'
5 | import {
6 | CodeLensesOptions,
7 | DebugLensesOptions,
8 | GetDescriptionCodeLensesOptions,
9 | SubmitCodeLensesOptions,
10 | TestCodeLensesOptions,
11 | ViewSubmitHistoryCodeLensesOptions,
12 | } from '../model/command'
13 | export class CodelensProvider implements vscode.CodeLensProvider {
14 | private codeLenses: vscode.CodeLens[] = []
15 | private regex: RegExp
16 | private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter()
17 | public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event
18 |
19 | constructor() {
20 | this.regex = /(@test)/g
21 |
22 | vscode.workspace.onDidChangeConfiguration((_) => {
23 | this._onDidChangeCodeLenses.fire()
24 | })
25 | }
26 | private getBuildCodeLenses(options: CodeLensesOptions): vscode.CodeLens {
27 | const codeLens = new vscode.CodeLens(new vscode.Range(0, 0, 0, 7))
28 |
29 | codeLens.command = {
30 | title: 'build',
31 | tooltip: 'build',
32 | command: 'algorithm.buildCode',
33 | arguments: [options],
34 | }
35 | return codeLens
36 | }
37 | private getSubmitCodeLenses(options: SubmitCodeLensesOptions): vscode.CodeLens {
38 | const codeLens = new vscode.CodeLens(new vscode.Range(0, 0, 0, 7))
39 |
40 | codeLens.command = {
41 | title: 'submit',
42 | tooltip: 'submit',
43 | command: 'algorithm.submit',
44 | arguments: [options],
45 | }
46 | return codeLens
47 | }
48 | private getDescriptionCodeLenses(options: GetDescriptionCodeLensesOptions) {
49 | const codeLens = new vscode.CodeLens(new vscode.Range(0, 0, 0, 7))
50 | codeLens.command = {
51 | title: 'description',
52 | tooltip: 'description',
53 | command: 'algorithm.getDescription',
54 | arguments: [options],
55 | }
56 | return codeLens
57 | }
58 | private getHistoryCodeLenses(options: ViewSubmitHistoryCodeLensesOptions) {
59 | const codeLens = new vscode.CodeLens(new vscode.Range(0, 0, 0, 7))
60 | codeLens.command = {
61 | title: 'history',
62 | tooltip: 'history',
63 | command: 'algorithm.viewSubmitHistory',
64 | arguments: [options],
65 | }
66 | return codeLens
67 | }
68 | private getTestCodeLenses(options: TestCodeLensesOptions) {
69 | const {
70 | testCaseParam: { line },
71 | } = options
72 | const codeLens = new vscode.CodeLens(new vscode.Range(line, 0, line, 7))
73 | codeLens.command = {
74 | title: 'test',
75 | tooltip: 'test',
76 | command: 'algorithm.testCode',
77 | arguments: [options],
78 | }
79 | return codeLens
80 | }
81 | private getDebugCodeLenses(testCaseParam: TestCaseParam, filePath: DebugLensesOptions) {
82 | const { line } = testCaseParam
83 | const codeLens = new vscode.CodeLens(new vscode.Range(line, 0, line, 7))
84 | codeLens.command = {
85 | title: 'debug',
86 | tooltip: 'debug',
87 | command: 'algorithm.debugCode',
88 | arguments: [filePath],
89 | }
90 | return codeLens
91 | }
92 |
93 | public provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] | Thenable {
94 | this.codeLenses = []
95 | const text = document.getText()
96 | const filePath = document.fileName
97 | const isSupport = isSupportFile(filePath)
98 | if (!isSupport) {
99 | return []
100 | }
101 | const enableExt = detectEnableExt(text, filePath)
102 |
103 | if (enableExt) {
104 | const codeLang = getFileLang(filePath)
105 | const langSlug = getFileLangSlug(filePath)
106 | const codeLensesOptions: CodeLensesOptions = {
107 | text,
108 | filePath,
109 | langSlug,
110 | }
111 | const submitCodeLenses = this.getSubmitCodeLenses({
112 | ...codeLensesOptions,
113 | })
114 | const buildCodeLenses = this.getBuildCodeLenses({
115 | ...codeLensesOptions,
116 | })
117 | const desCodeLenses = this.getDescriptionCodeLenses({
118 | ...codeLensesOptions,
119 | })
120 | const historyCodeLenses = this.getHistoryCodeLenses({
121 | ...codeLensesOptions,
122 | })
123 | this.codeLenses.push(submitCodeLenses)
124 | this.codeLenses.push(buildCodeLenses)
125 | this.codeLenses.push(desCodeLenses)
126 | this.codeLenses.push(historyCodeLenses)
127 | if (isAlgorithm(codeLang)) {
128 | const lang = new Service(filePath, text)
129 | const testCaseList = lang.getTestCaseList(text)
130 | testCaseList.forEach((testCaseParam) => {
131 | const testCodeOptions: TestCodeLensesOptions = {
132 | filePath: document.uri.fsPath,
133 | testCaseParam,
134 | }
135 | const testCodeLenses = this.getTestCodeLenses(testCodeOptions)
136 | const debugCodeLenses = this.getDebugCodeLenses(testCaseParam, document.uri.fsPath)
137 | this.codeLenses.push(testCodeLenses)
138 | this.codeLenses.push(debugCodeLenses)
139 | })
140 | }
141 |
142 | return this.codeLenses
143 | }
144 | return []
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/provider/memoProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { config, updateEnv } from '../config'
3 | import { sortFiles } from '../util'
4 | const MemoFilePreviewCommand = 'algorithm.memoFilePreview'
5 | const MemoFileContext = 'memoFile'
6 | enum MemoLevel {
7 | Folder,
8 | File,
9 | Invalid,
10 | }
11 | enum RemoveMemoMsg {
12 | Folder = 'Are you sure you want to delete the folder?',
13 | File = 'Are you sure you want to delete the file?',
14 | }
15 |
16 | export class MemoTree extends vscode.TreeItem {
17 | constructor(
18 | public label: string,
19 | public id: string,
20 | public paths: string[],
21 | public collapsibleState: vscode.TreeItemCollapsibleState = vscode.TreeItemCollapsibleState.Collapsed,
22 | public command?: vscode.Command
23 | ) {
24 | super(label, vscode.TreeItemCollapsibleState.Collapsed)
25 | }
26 | contextValue = 'memo'
27 | }
28 |
29 | export class MemoProvider implements vscode.TreeDataProvider {
30 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter<
31 | MemoTree | undefined
32 | >()
33 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event
34 | refresh(): void {
35 | this._onDidChangeTreeData.fire(undefined)
36 | }
37 | getTreeItem(element: MemoTree): vscode.TreeItem {
38 | return element
39 | }
40 | getChildren(element?: MemoTree): Thenable {
41 | if (element) {
42 | const folderName = element.label
43 | const files = this.getFolderFiles(folderName)
44 | return Promise.resolve(
45 | files.map((f) => {
46 | const tree = new MemoTree(f.label, f.id, f.paths, vscode.TreeItemCollapsibleState.None, {
47 | title: 'memoFilePreview',
48 | command: MemoFilePreviewCommand,
49 | arguments: [f.param],
50 | })
51 | tree.contextValue = MemoFileContext
52 | return tree
53 | })
54 | )
55 | } else {
56 | const folders = this.getFolders()
57 | return Promise.resolve(
58 | folders.map((f) => {
59 | const tree = new MemoTree(f.label, f.id, f.paths)
60 | tree.contextValue = MemoFileContext
61 | return tree
62 | })
63 | )
64 | }
65 | }
66 | getFolders() {
67 | return config.env.memo.map((v) => {
68 | return {
69 | label: v.name,
70 | id: 'folder' + v.name,
71 | paths: [v.name],
72 | }
73 | })
74 | }
75 | getFolderFiles(name: string) {
76 | const folder = config.env.memo.find((v) => v.name === name)
77 | if (folder) {
78 | const files = folder.children
79 | sortFiles(files)
80 | return files.map((v) => {
81 | return {
82 | label: v.name,
83 | id: name + v.name,
84 | paths: [name, v.name],
85 | param: v,
86 | }
87 | })
88 | } else {
89 | return []
90 | }
91 | }
92 | static getElementLevel(element: MemoTree) {
93 | const paths = element.paths
94 | if (paths.length === 1) {
95 | return MemoLevel.Folder
96 | } else if (paths.length === 2) {
97 | return MemoLevel.File
98 | }
99 | return MemoLevel.Invalid
100 | }
101 | static async remove(element: MemoTree): Promise {
102 | const isRemove = await MemoProvider.checkRemove(element)
103 | if (!isRemove) {
104 | return false
105 | }
106 | const removeConfigs = [
107 | {
108 | level: MemoLevel.Folder,
109 | fn: MemoProvider.removeFolder,
110 | },
111 | {
112 | level: MemoLevel.File,
113 | fn: MemoProvider.removeFile,
114 | },
115 | ]
116 | const level = MemoProvider.getElementLevel(element)
117 | const config = removeConfigs.find((c) => c.level === level)
118 | if (config) {
119 | config.fn(element.paths)
120 | return true
121 | }
122 | return false
123 | }
124 |
125 | static async checkRemove(element: MemoTree): Promise {
126 | const msgConfigs = [
127 | {
128 | level: MemoLevel.Folder,
129 | msg: RemoveMemoMsg.Folder,
130 | },
131 | {
132 | level: MemoLevel.File,
133 | msg: RemoveMemoMsg.File,
134 | },
135 | ]
136 | const Remove = 'remove'
137 | const level = MemoProvider.getElementLevel(element)
138 | const config = msgConfigs.find((c) => c.level === level)
139 | if (config) {
140 | const msg = config.msg
141 | const r = await vscode.window.showWarningMessage(msg, { modal: true }, Remove)
142 | return r === Remove
143 | }
144 | return false
145 | }
146 | static removeFolder(paths: string[]) {
147 | const memo = config.env.memo
148 | const folderName = paths[0]
149 | const index = memo.findIndex((m) => m.name === folderName)
150 | if (index !== -1) {
151 | memo.splice(index, 1)
152 | return updateEnv('memo', memo)
153 | }
154 |
155 | config.log.appendLine('folder not exist')
156 | }
157 | static removeFile(paths: string[]) {
158 | const memo = config.env.memo
159 | const [folderName, fileName] = paths
160 | const folder = memo.find((m) => m.name === folderName)
161 | if (folder) {
162 | const index = folder.children.findIndex((f) => f.name === fileName)
163 | if (index !== -1) {
164 | folder.children.splice(index, 1)
165 | return updateEnv('memo', memo)
166 | }
167 | }
168 | config.log.appendLine('file not exist')
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/template/cpp/parse_test.cpp:
--------------------------------------------------------------------------------
1 | #include "parse.h"
2 | #include
3 | #include "algm.h"
4 | using namespace std;
5 |
6 | string transformBool(bool t)
7 | {
8 | if (t == 1)
9 | {
10 | return "ok";
11 | }
12 | else
13 | {
14 | return "fail";
15 | }
16 | }
17 |
18 | bool deepEqual(vector &a, vector &b)
19 | {
20 | if (a.size() != b.size())
21 | {
22 | return false;
23 | }
24 | for (int i = 0; i < a.size(); i++)
25 | {
26 | if (a[i] != b[i])
27 | {
28 | return false;
29 | }
30 | }
31 | return true;
32 | }
33 | bool deepEqual(vector &a, vector &b)
34 | {
35 | if (a.size() != b.size())
36 | {
37 | return false;
38 | }
39 | for (int i = 0; i < a.size(); i++)
40 | {
41 | if (a[i] != b[i])
42 | {
43 | return false;
44 | }
45 | }
46 | return true;
47 | }
48 | bool deepEqual(vector> &a, vector> &b)
49 | {
50 | if (a.size() != b.size())
51 | {
52 | return false;
53 | }
54 | for (int i = 0; i < a.size(); i++)
55 | {
56 | if (!deepEqual(a[i], b[i]))
57 | {
58 | return false;
59 | }
60 | }
61 | return true;
62 | }
63 | bool deepEqual(vector> &a, vector> &b)
64 | {
65 | if (a.size() != b.size())
66 | {
67 | return false;
68 | }
69 | for (int i = 0; i < a.size(); i++)
70 | {
71 | if (!deepEqual(a[i], b[i]))
72 | {
73 | return false;
74 | }
75 | }
76 | return true;
77 | }
78 | int main()
79 | {
80 | int num = parseInteger("1");
81 | cout << "test parseInteger: " << transformBool(num == 1) << endl;
82 |
83 | vector nums = parseIntegerArr("[1,2,3]");
84 | vector target{1, 2, 3};
85 | cout << "test parseIntegerArr: " << transformBool(deepEqual(nums, target)) << endl;
86 |
87 | vector> nums2 = parseIntegerArrArr("[[1,2,3],[4,5,6]]");
88 | vector> target2{vector{1, 2, 3}, vector{4, 5, 6}};
89 | cout << "test parseIntegerArrArr: " << transformBool(deepEqual(nums2, target2)) << endl;
90 |
91 | string str3 = parseString("\"abc\\\"\"");
92 | string target3 = "abc\"";
93 | cout << "test parseString: " << transformBool(str3 == target3) << endl;
94 |
95 | vector str4 = parseStringArr("[\"abc\\\"\"]");
96 | vector target4{"abc\""};
97 | cout << "test parseStringArr: " << transformBool(deepEqual(str4, target4)) << endl;
98 |
99 | vector> str5 = parseStringArrArr("[[\"1\",\"2\",\"3\"],[\"4\",\"5\",\"6\"]]");
100 | vector> target5{vector{"1", "2", "3"}, vector{"4", "5", "6"}};
101 | cout << "test parseStringArrArr: " << transformBool(deepEqual(str5, target5)) << endl;
102 |
103 | string origin6 = "[1,2,null,3]";
104 | TreeNode *treeNode6 = parseTreeNode("[1,2,null,3]");
105 | string target6 = serializeTreeNode(treeNode6);
106 | cout << "test parseTreeNode: " << transformBool(origin6 == target6) << endl;
107 |
108 | string origin7 = "[1,2,3]";
109 | ListNode *listNode7 = parseListNode(origin7);
110 | string target7 = serializeListNode(listNode7);
111 | cout << "test parseListNode: " << transformBool(origin7 == target7) << endl;
112 |
113 | string origin8 = "[[1,2,3]]";
114 | vector listNodeArr8 = parseListNodeArr(origin8);
115 | string target8 = serializeListNodeArr(listNodeArr8);
116 | cout << "test parseListNodeArr: " << transformBool(origin8 == target8) << endl;
117 |
118 | string origin9 = "[[1,2,null,3],[1,2,3]]";
119 | vector listNodeArr9 = parseTreeNodeArr(origin9);
120 | string target9 = serializeTreeNodeArr(listNodeArr9);
121 | cout << "test parseTreeNodeArr: " << transformBool(origin9 == target9) << endl;
122 |
123 | string origin10 = "2.00000";
124 | double num10 = parseFloat(origin10);
125 | string target10 = serializeFloat(num10);
126 | cout << "test serializeFloat: " << transformBool(origin10 == target10) << endl;
127 |
128 | char char11 = parseChar("\"a\"");
129 | char target11 = 'a';
130 | cout << "test parseString: " << transformBool(char11 == target11) << endl;
131 |
132 | string origin12 = "\"a\"";
133 | char char12 = parseChar(origin12);
134 | string str12 = serializeChar(char12);
135 | cout << "test parseChar: " << transformBool(str12 == origin12) << endl;
136 |
137 | string origin13 = "[\"a\",\"v\"]";
138 | vector charArr13 = parseCharArr(origin13);
139 | string str13 = serializeCharArr(charArr13);
140 | cout << "test parseCharArr: " << transformBool(str13 == origin13) << endl;
141 |
142 | string origin14 = "[[\"a\",\"v\"]]";
143 | vector> charArrArr14 = parseCharArrArr(origin14);
144 | string str14 = serializeCharArrArr(charArrArr14);
145 | cout << "test parseCharArrArr: " << transformBool(str14 == origin14) << endl;
146 | }
--------------------------------------------------------------------------------
/media/markdown.css:
--------------------------------------------------------------------------------
1 | /**
2 | * copy from https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css
3 | */
4 |
5 | /*---------------------------------------------------------------------------------------------
6 | * Copyright (c) Microsoft Corporation. All rights reserved.
7 | * Licensed under the MIT License. See License.txt in the project root for license information.
8 | *--------------------------------------------------------------------------------------------*/
9 |
10 | html, body {
11 | font-family: var(--markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif);
12 | font-size: var(--markdown-font-size, 14px);
13 | padding: 0 26px;
14 | line-height: var(--markdown-line-height, 22px);
15 | word-wrap: break-word;
16 | }
17 |
18 | #code-csp-warning {
19 | position: fixed;
20 | top: 0;
21 | right: 0;
22 | color: white;
23 | margin: 16px;
24 | text-align: center;
25 | font-size: 12px;
26 | font-family: sans-serif;
27 | background-color:#444444;
28 | cursor: pointer;
29 | padding: 6px;
30 | box-shadow: 1px 1px 1px rgba(0,0,0,.25);
31 | }
32 |
33 | #code-csp-warning:hover {
34 | text-decoration: none;
35 | background-color:#007acc;
36 | box-shadow: 2px 2px 2px rgba(0,0,0,.25);
37 | }
38 |
39 | body.scrollBeyondLastLine {
40 | margin-bottom: calc(100vh - 22px);
41 | }
42 |
43 | body.showEditorSelection .code-line {
44 | position: relative;
45 | }
46 |
47 | body.showEditorSelection .code-active-line:before,
48 | body.showEditorSelection .code-line:hover:before {
49 | content: "";
50 | display: block;
51 | position: absolute;
52 | top: 0;
53 | left: -12px;
54 | height: 100%;
55 | }
56 |
57 | body.showEditorSelection li.code-active-line:before,
58 | body.showEditorSelection li.code-line:hover:before {
59 | left: -30px;
60 | }
61 |
62 | .vscode-light.showEditorSelection .code-active-line:before {
63 | border-left: 3px solid rgba(0, 0, 0, 0.15);
64 | }
65 |
66 | .vscode-light.showEditorSelection .code-line:hover:before {
67 | border-left: 3px solid rgba(0, 0, 0, 0.40);
68 | }
69 |
70 | .vscode-light.showEditorSelection .code-line .code-line:hover:before {
71 | border-left: none;
72 | }
73 |
74 | .vscode-dark.showEditorSelection .code-active-line:before {
75 | border-left: 3px solid rgba(255, 255, 255, 0.4);
76 | }
77 |
78 | .vscode-dark.showEditorSelection .code-line:hover:before {
79 | border-left: 3px solid rgba(255, 255, 255, 0.60);
80 | }
81 |
82 | .vscode-dark.showEditorSelection .code-line .code-line:hover:before {
83 | border-left: none;
84 | }
85 |
86 | .vscode-high-contrast.showEditorSelection .code-active-line:before {
87 | border-left: 3px solid rgba(255, 160, 0, 0.7);
88 | }
89 |
90 | .vscode-high-contrast.showEditorSelection .code-line:hover:before {
91 | border-left: 3px solid rgba(255, 160, 0, 1);
92 | }
93 |
94 | .vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
95 | border-left: none;
96 | }
97 |
98 | img {
99 | max-width: 100%;
100 | max-height: 100%;
101 | }
102 |
103 | a {
104 | text-decoration: none;
105 | }
106 |
107 | a:hover {
108 | text-decoration: underline;
109 | }
110 |
111 | a:focus,
112 | input:focus,
113 | select:focus,
114 | textarea:focus {
115 | outline: 1px solid -webkit-focus-ring-color;
116 | outline-offset: -1px;
117 | }
118 |
119 | hr {
120 | border: 0;
121 | height: 2px;
122 | border-bottom: 2px solid;
123 | }
124 |
125 | h1 {
126 | padding-bottom: 0.3em;
127 | line-height: 1.2;
128 | border-bottom-width: 1px;
129 | border-bottom-style: solid;
130 | }
131 |
132 | h1, h2, h3 {
133 | font-weight: normal;
134 | }
135 |
136 | table {
137 | border-collapse: collapse;
138 | }
139 |
140 | table > thead > tr > th {
141 | text-align: left;
142 | border-bottom: 1px solid;
143 | }
144 |
145 | table > thead > tr > th,
146 | table > thead > tr > td,
147 | table > tbody > tr > th,
148 | table > tbody > tr > td {
149 | padding: 5px 10px;
150 | }
151 |
152 | table > tbody > tr + tr > td {
153 | border-top: 1px solid;
154 | }
155 |
156 | blockquote {
157 | margin: 0 7px 0 5px;
158 | padding: 0 16px 0 10px;
159 | border-left-width: 5px;
160 | border-left-style: solid;
161 | }
162 |
163 | code {
164 | font-family: var(--vscode-editor-font-family, "SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace);
165 | font-size: 1em;
166 | line-height: 1.357em;
167 | }
168 |
169 | body.wordWrap pre {
170 | white-space: pre-wrap;
171 | }
172 |
173 | pre:not(.hljs),
174 | pre.hljs code > div {
175 | padding: 16px;
176 | border-radius: 3px;
177 | overflow: auto;
178 | }
179 |
180 | pre code {
181 | color: var(--vscode-editor-foreground);
182 | tab-size: 4;
183 | }
184 |
185 | /** Theming */
186 |
187 | .vscode-light pre {
188 | background-color: rgba(220, 220, 220, 0.4);
189 | }
190 |
191 | .vscode-dark pre {
192 | background-color: rgba(10, 10, 10, 0.4);
193 | }
194 |
195 | .vscode-high-contrast pre {
196 | background-color: rgb(0, 0, 0);
197 | }
198 |
199 | .vscode-high-contrast h1 {
200 | border-color: rgb(0, 0, 0);
201 | }
202 |
203 | .vscode-light table > thead > tr > th {
204 | border-color: rgba(0, 0, 0, 0.69);
205 | }
206 |
207 | .vscode-dark table > thead > tr > th {
208 | border-color: rgba(255, 255, 255, 0.69);
209 | }
210 |
211 | .vscode-light h1,
212 | .vscode-light hr,
213 | .vscode-light table > tbody > tr + tr > td {
214 | border-color: rgba(0, 0, 0, 0.18);
215 | }
216 |
217 | .vscode-dark h1,
218 | .vscode-dark hr,
219 | .vscode-dark table > tbody > tr + tr > td {
220 | border-color: rgba(255, 255, 255, 0.18);
221 | }
222 |
--------------------------------------------------------------------------------
/src/webview/buildCodeWebview.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as vscode from 'vscode'
3 | import { Clipboard, env } from 'vscode'
4 | import { highlightCode } from '../util'
5 | // eslint-disable-next-line @typescript-eslint/no-var-requires
6 |
7 | class BuildCodePanel {
8 | /**
9 | * Track the currently panel. Only allow a single panel to exist at a time.
10 | */
11 | public static currentPanel: BuildCodePanel | undefined
12 | private static readonly buildCodeActiveContextKey = 'buildCodeFocus'
13 | public static readonly viewType = 'buildCodeView'
14 |
15 | private readonly _panel: vscode.WebviewPanel
16 | private readonly _extensionPath: string
17 | private context: vscode.ExtensionContext
18 | public text: string
19 | public langSlug: string
20 | private clipboard: Clipboard = env.clipboard
21 | private _disposables: vscode.Disposable[] = []
22 | public static commands = new Map()
23 | private setbuildCodeActiveContext(value: boolean) {
24 | vscode.commands.executeCommand('setContext', BuildCodePanel.buildCodeActiveContextKey, value)
25 | }
26 | private registerCommand(id: string, impl: (...args: unknown[]) => void, thisArg?: unknown) {
27 | if (!BuildCodePanel.commands.get(id)) {
28 | const dispose = vscode.commands.registerCommand(id, impl, thisArg)
29 | this.context.subscriptions.push(dispose)
30 | BuildCodePanel.commands.set(id, dispose)
31 | }
32 | }
33 | public static createOrShow(context: vscode.ExtensionContext, text: string, langSlug: string) {
34 | const column = vscode.ViewColumn.Two
35 | const extensionPath = context.extensionPath
36 | // If we already have a panel, show it.
37 |
38 | if (BuildCodePanel.currentPanel) {
39 | BuildCodePanel.currentPanel._panel.reveal(column)
40 | BuildCodePanel.currentPanel.update(text, langSlug)
41 | return
42 | }
43 |
44 | // Otherwise, create a new panel.
45 | const panel = vscode.window.createWebviewPanel(BuildCodePanel.viewType, 'code', vscode.ViewColumn.Two, {
46 | // Enable javascript in the webview
47 | enableScripts: true,
48 | localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'media'))],
49 | })
50 |
51 | BuildCodePanel.currentPanel = new BuildCodePanel(panel, context, text, langSlug)
52 | }
53 |
54 | private constructor(panel: vscode.WebviewPanel, context: vscode.ExtensionContext, text: string, langSlug: string) {
55 | this._panel = panel
56 | this._extensionPath = context.extensionPath
57 | this.context = context
58 | this.text = text
59 | this.langSlug = langSlug
60 | this.registerCommand('algorithm.copyCode', () => {
61 | this.clipboard.writeText(this.text)
62 | })
63 |
64 | this.initView()
65 | // Set the webview's initial html content
66 | this._update()
67 |
68 | // Listen for when the panel is disposed
69 | // This happens when the user closes the panel or when the panel is closed programatically
70 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables)
71 |
72 | // Update the content based on view changes
73 | this._panel.onDidChangeViewState(
74 | (e) => {
75 | this.setbuildCodeActiveContext(e.webviewPanel.active)
76 | if (e.webviewPanel.active) {
77 | this._update()
78 | }
79 | },
80 | null,
81 | this._disposables
82 | )
83 | }
84 |
85 | public dispose() {
86 | BuildCodePanel.currentPanel = undefined
87 |
88 | // Clean up our resources
89 | this._panel.dispose()
90 | this.setbuildCodeActiveContext(false)
91 | while (this._disposables.length) {
92 | const x = this._disposables.pop()
93 | if (x) {
94 | x.dispose()
95 | }
96 | }
97 | }
98 | public update(text: string, langSlug: string) {
99 | this.text = text
100 | this.langSlug = langSlug
101 | this._update()
102 | }
103 | private getWebviewUri(name: string) {
104 | const webview = this._panel.webview
105 | const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'media', name))
106 | return webview.asWebviewUri(scriptPathOnDisk)
107 | }
108 | private initView() {
109 | const nonce = getNonce()
110 |
111 | // And the uri we use to load this script in the webview
112 | const buildCodeUri = this.getWebviewUri('buildcode.js')
113 | const scriptUri = this.getWebviewUri('highlight.min.js')
114 | const cssUri = this.getWebviewUri('highlight.css')
115 | this._panel.webview.html = `
116 |
117 |
118 |
119 |
123 |
124 |
125 | Cat Coding
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | `
136 | }
137 | private _update() {
138 | this.setbuildCodeActiveContext(true)
139 | const code = highlightCode(this.text, this.langSlug)
140 | this._panel.webview.postMessage({ command: 'newCode', data: code })
141 | }
142 | }
143 |
144 | function getNonce() {
145 | let text = ''
146 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
147 | for (let i = 0; i < 32; i++) {
148 | text += possible.charAt(Math.floor(Math.random() * possible.length))
149 | }
150 | return text
151 | }
152 |
153 | export function createPanel(context: vscode.ExtensionContext, text: string, langSlug: string) {
154 | BuildCodePanel.createOrShow(context, text, langSlug)
155 | }
156 |
--------------------------------------------------------------------------------
/src/common/langConfig.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 |
3 | const langsConfig = {
4 | algorithms: [
5 | {
6 | lang: 'C++',
7 | langSlug: 'cpp',
8 | ext: '.cpp',
9 | comment: '//',
10 | fileNameSep: '.',
11 | },
12 | {
13 | lang: 'Java',
14 | langSlug: 'java',
15 | ext: '.java',
16 | comment: '//',
17 | fileNameSep: '.',
18 | },
19 | {
20 | lang: 'Python',
21 | langSlug: 'python',
22 | ext: '.py',
23 | comment: '#',
24 | fileNameSep: '_',
25 | },
26 | {
27 | lang: 'Python3',
28 | langSlug: 'python3',
29 | ext: '.py',
30 | comment: '#',
31 | fileNameSep: '_',
32 | },
33 | {
34 | lang: 'C',
35 | langSlug: 'c',
36 | ext: '.c',
37 | comment: '//',
38 | fileNameSep: '.',
39 | },
40 | {
41 | lang: 'C#',
42 | langSlug: 'csharp',
43 | ext: '.cs',
44 | comment: '//',
45 | fileNameSep: '.',
46 | },
47 | {
48 | lang: 'JavaScript',
49 | langSlug: 'javascript',
50 | ext: '.js',
51 | comment: '//',
52 | fileNameSep: '.',
53 | },
54 | {
55 | lang: 'Ruby',
56 | langSlug: 'ruby',
57 | ext: '.rb',
58 | comment: '#',
59 | fileNameSep: '.',
60 | },
61 | {
62 | lang: 'Swift',
63 | langSlug: 'swift',
64 | ext: '.swift',
65 | comment: '//',
66 | fileNameSep: '.',
67 | },
68 | {
69 | lang: 'Go',
70 | langSlug: 'golang',
71 | ext: '.go',
72 | comment: '//',
73 | fileNameSep: '.',
74 | },
75 | {
76 | lang: 'Scala',
77 | langSlug: 'scala',
78 | ext: '.scala',
79 | comment: '//',
80 | fileNameSep: '.',
81 | },
82 | {
83 | lang: 'Kotlin',
84 | langSlug: 'kotlin',
85 | ext: '.kt',
86 | comment: '//',
87 | fileNameSep: '.',
88 | },
89 | {
90 | lang: 'Rust',
91 | langSlug: 'rust',
92 | ext: '.rs',
93 | comment: '//',
94 | fileNameSep: '.',
95 | },
96 | {
97 | lang: 'PHP',
98 | langSlug: 'php',
99 | ext: '.php',
100 | comment: '//',
101 | fileNameSep: '.',
102 | },
103 | {
104 | lang: 'TypeScript',
105 | langSlug: 'typescript',
106 | ext: '.ts',
107 | comment: '//',
108 | fileNameSep: '.',
109 | },
110 | ],
111 | database: [
112 | {
113 | lang: 'MySQL',
114 | langSlug: 'mysql',
115 | ext: '.sql',
116 | comment: '#',
117 | fileNameSep: '.',
118 | },
119 | {
120 | lang: 'MS SQL Server',
121 | langSlug: 'mssql',
122 | ext: '.sql',
123 | comment: '#',
124 | fileNameSep: '.',
125 | },
126 | {
127 | lang: 'Oracle',
128 | langSlug: 'oraclesql',
129 | ext: '.sql',
130 | comment: '#',
131 | fileNameSep: '.',
132 | },
133 | ],
134 | shell: [
135 | {
136 | lang: 'Bash',
137 | langSlug: 'bash',
138 | ext: '.sh',
139 | comment: '#',
140 | fileNameSep: '.',
141 | },
142 | ],
143 | }
144 | enum LangBaseComment {
145 | Slash = '//',
146 | Pound = '#',
147 | }
148 | export interface LangBase {
149 | lang: CodeLang
150 | langSlug: string
151 | ext: string
152 | comment: LangBaseComment
153 | fileNameSep: '.' | '_'
154 | }
155 | export interface LangMap {
156 | [langSlug: string]: LangBase
157 | }
158 | interface LangExtMap {
159 | [ext: string]: LangBase
160 | }
161 | const allLangs = Object.values(langsConfig).reduce((prev, cur) => prev.concat(cur))
162 | export const langMap: LangMap = allLangs.reduce((prev, cur) => (prev[cur.langSlug] = cur) && prev, {})
163 | export const langExtMap: LangExtMap = allLangs.reduce((prev, cur) => (prev[cur.ext] = cur) && prev, {})
164 | export enum CodeLang {
165 | 'C++' = 'C++',
166 | 'C#' = 'C#',
167 | Java = 'Java',
168 | Python = 'Python',
169 | Python3 = 'Python3',
170 | C = 'C',
171 | JavaScript = 'JavaScript',
172 | Ruby = 'Ruby',
173 | Swift = 'Swift',
174 | Go = 'Go',
175 | Scala = 'Scala',
176 | Kotlin = 'Kotlin',
177 | Rust = 'Rust',
178 | PHP = 'PHP',
179 | TypeScript = 'TypeScript',
180 | }
181 | export enum DataBase {
182 | MySQL = 'MySQL',
183 | 'MS SQL Server' = 'MS SQL Server',
184 | 'Oracle' = 'Oracle',
185 | }
186 |
187 | export function getFileLang(filePath: string): CodeLang {
188 | const ext = path.extname(filePath)
189 | const langItem = langExtMap[ext]
190 | if (!langItem) {
191 | throw new Error('file extname invalid')
192 | }
193 | return langItem.lang
194 | }
195 |
196 | export function getFileLangSlug(filePath: string): string {
197 | const ext = path.extname(filePath)
198 | const langItem = langExtMap[ext]
199 | if (!langItem) {
200 | throw new Error('file extname invalid')
201 | }
202 | return langItem.langSlug
203 | }
204 | export function isDataBase(filePath: string): boolean {
205 | const ext = path.extname(filePath)
206 | return ext === '.sql'
207 | }
208 | export function isShell(filePath: string): boolean {
209 | const ext = path.extname(filePath)
210 | return ext === '.sh'
211 | }
212 | export function isAlgorithm(lang: CodeLang): boolean {
213 | return !!langsConfig.algorithms.find((alg) => alg.lang === lang)
214 | }
215 | export function getFileComment(filePath: string): LangBaseComment {
216 | const ext = path.extname(filePath)
217 | const langItem = langExtMap[ext]
218 | if (!langItem) {
219 | throw new Error('file extname invalid')
220 | }
221 | return langItem.comment
222 | }
223 |
224 | export const builtInLang = [CodeLang.JavaScript, CodeLang.TypeScript]
225 | export const otherLang = [CodeLang.Python3, CodeLang.Go, CodeLang.Java, CodeLang['C++']]
226 | export const enableLang = [...builtInLang, ...otherLang]
227 | export const databases = [DataBase['MS SQL Server'], DataBase.MySQL, DataBase.Oracle]
228 | export const enNameLangs = [CodeLang.Java, CodeLang['C++']]
229 | export enum ExtraType {
230 | ListNode = 'ListNode',
231 | TreeNode = 'TreeNode',
232 | }
233 | export const highlightLangMap = {
234 | cpp: 'cpp',
235 | java: 'java',
236 | python: 'python',
237 | python3: 'python',
238 | c: 'c',
239 | csharp: 'csharp',
240 | javascript: 'javascript',
241 | ruby: 'ruby',
242 | swift: 'swift',
243 | golang: 'go',
244 | scala: 'scala',
245 | kotlin: 'kotlin',
246 | rust: 'rust',
247 | php: 'php',
248 | typescript: 'typescript',
249 | }
250 | export function transformToHightlightLang(lang: string): string {
251 | return highlightLangMap[lang] || lang
252 | }
253 | export function isSupportFile(filePath: string) {
254 | if (isShell(filePath) || isDataBase(filePath)) {
255 | return true
256 | }
257 | const ext = path.extname(filePath)
258 |
259 | const langItem = langExtMap[ext]
260 | if (!langItem) {
261 | return false
262 | }
263 | const lang = langItem.lang
264 | return enableLang.includes(lang)
265 | }
266 |
--------------------------------------------------------------------------------
/src/model/question.cn.ts:
--------------------------------------------------------------------------------
1 | import { CodeSnippet, FlagType, GraphqlVariables, SubmissionComment } from './common'
2 |
3 | export interface ConciseQuestion {
4 | fid: string
5 | level: number
6 | id: number
7 | title: string
8 | slug: string
9 | acs: number
10 | submitted: number
11 | paid_only: boolean
12 | status: string
13 | name: string
14 | }
15 | export interface MapIdConciseQuestion {
16 | [id: number]: ConciseQuestion
17 | }
18 |
19 | export interface TodayRecordData {
20 | todayRecord: TodayRecord[]
21 | }
22 | export interface TodayRecord {
23 | question: Pick
24 | lastSubmission: LastSubmission
25 | date: string
26 | userStatus: UserStatus
27 | __typename: string
28 | }
29 | interface LastSubmission {
30 | id: string
31 | __typename: string
32 | }
33 | type UserStatus = 'FINISH' | 'NOT_START'
34 |
35 | export interface QuestionTranslationData {
36 | translations: Translation[]
37 | }
38 | export interface Translation {
39 | questionId: string
40 | title: string
41 | }
42 |
43 | export interface GraphqlResponse {
44 | data: T
45 | }
46 | export interface DailyQuestionRecordData {
47 | dailyQuestionRecords: [DailyQuestionRecord]
48 | }
49 | export interface DailyQuestionRecord {
50 | date: string
51 |
52 | question: Pick
53 | userStatus: string
54 | __typename: string
55 | }
56 |
57 | export interface GraphqlRequestData {
58 | operationName: string | null
59 | query: string
60 | variables: GraphqlVariables
61 | }
62 |
63 | export interface QuestionData {
64 | question: Question
65 | }
66 | export interface Question {
67 | questionSourceContent?: string
68 | questionId: string
69 | questionFrontendId: string
70 | boundTopicId: number
71 | title: string
72 | titleSlug: string
73 | content: string
74 | translatedTitle: string
75 | translatedContent: string
76 | isPaidOnly: boolean
77 | difficulty: string
78 | likes: number
79 | dislikes: number
80 | isLiked: unknown
81 | similarQuestions: string
82 | contributors: []
83 | langToValidPlayground: string
84 | topicTags: TopicTags[]
85 | companyTagStats: unknown
86 | codeSnippets: CodeSnippet[]
87 | stats: string
88 | hints: string[]
89 | solution: Solution
90 | status: Status
91 | sampleTestCase: string
92 | metaData: string
93 | judgerAvailable: boolean
94 | judgeType: string
95 | mysqlSchemas: string[]
96 | enableRunCode: boolean
97 | envInfo: string
98 | book: unknown
99 | isSubscribed: boolean
100 | isDailyQuestion: boolean
101 | dailyRecordStatus: string
102 | editorType: string
103 | ugcQuestionId: unknown
104 | style: string
105 |
106 | // questionTitleSlug: string
107 | }
108 | interface Solution {
109 | canSeeDetail: boolean
110 | id: string
111 | }
112 | type Status = 'ac' | 'notac' | null
113 |
114 | interface TopicTags {
115 | name: string
116 | slug: string
117 | translatedName: string
118 | questions: number[] //question id
119 | __typename: string
120 | }
121 |
122 | export interface ContestData {
123 | allContests: Pick[]
124 | }
125 | interface Contest {
126 | containsPremium: boolean
127 | title: string
128 | cardImg: string
129 | titleSlug: string
130 | description: string
131 | startTime: number
132 | duration: number
133 | originStartTime: number
134 | isVirtual: boolean
135 | company: Company
136 | __typename: string
137 | }
138 | interface Company {
139 | watermark: string
140 | __typename: string
141 | }
142 |
143 | export interface TagData {
144 | topics: TopicTags[]
145 | }
146 | // interface Topics {
147 | // slug: string
148 | // name: string
149 | // questions: number[]
150 | // translatedName: string
151 | // }
152 |
153 | export interface SubmitOptions {
154 | titleSlug: string
155 | typed_code: string
156 | question_id: string
157 | lang?: string
158 | }
159 | export interface SubmitContestOptions extends SubmitOptions {
160 | weekname: string
161 | }
162 | export interface SubmitResponse {
163 | submission_id: number
164 | }
165 | export interface CheckResponse {
166 | status_code: number
167 | lang: string
168 | run_success: boolean
169 | status_runtime: string
170 | memory: number
171 | question_id: string
172 | elapsed_time: number
173 | compare_result: string
174 | code_output: string
175 | std_output: string
176 | last_testcase: string
177 | task_finish_time: number
178 | task_name: string
179 | finished: boolean
180 | status_msg: string
181 | state: State
182 | fast_submit: boolean
183 | total_correct: number
184 | total_testcases: number
185 | submission_id: string
186 | runtime_percentile: number
187 | status_memory: string
188 | memory_percentile: number
189 | pretty_lang: string
190 | }
191 |
192 | export interface CheckContestResponse {
193 | state: State
194 | }
195 | type State = 'PENDING' | 'STARTED' | 'SUCCESS'
196 |
197 | export interface CheckOptions {
198 | submission_id: number
199 | titleSlug: string
200 | }
201 | export interface CheckContestOptions extends CheckOptions {
202 | weekname: string
203 | }
204 |
205 | export interface SubmissionsResponse {
206 | submissionList: SubmissionList
207 | }
208 | export interface SubmissionsOptions {
209 | lastKey?: string
210 | limit?: number
211 | offset?: number
212 | titleSlug: string
213 | }
214 | interface SubmissionList {
215 | lastKey: string
216 | hasNext: boolean
217 | submissions: Submissions[]
218 | }
219 | interface Submissions {
220 | id: string
221 | statusDisplay: string
222 | lang: string
223 | runtime: string
224 | timestamp: string
225 | url: string
226 | isPending: string
227 | memory: string
228 | submissionComment?: SubmissionComment
229 | }
230 | export interface UpdateCommentOptions {
231 | comment: string
232 | flagType?: FlagType
233 | submissionId: string
234 | }
235 | export interface UpdateCommentResponse {
236 | submissionCreateOrUpdateSubmissionComment: SubmissionCreateOrUpdateSubmissionComment
237 | }
238 |
239 | interface SubmissionCreateOrUpdateSubmissionComment {
240 | ok: boolean
241 | }
242 |
243 | export interface SubmissionDetailOptions {
244 | id: string
245 | }
246 | export interface SubmissionDetailResponse {
247 | submissionDetail?: SubmissionDetail
248 | }
249 | interface SubmissionDetail {
250 | id: string
251 | code: string
252 | runtime: string
253 | memory: string
254 | rawMemory: string
255 | statusDisplay: string
256 | timestamp: number
257 | lang: string
258 | passedTestCaseCnt: number
259 | totalTestCaseCnt: number
260 | sourceUrl: string
261 | question: Question
262 | outputDetail: OutputDetail
263 | __typename: string
264 | submissionComment: SubmissionComment
265 | }
266 |
267 | interface OutputDetail {
268 | codeOutput: string
269 | expectedOutput: string
270 | input: string
271 | compileError: string
272 | runtimeError: string
273 | lastTestcase: string
274 | __typename: string
275 | }
276 |
--------------------------------------------------------------------------------
/src/login/index.ts:
--------------------------------------------------------------------------------
1 | import _request = require('request')
2 | import { promisify } from 'util'
3 | import { window } from 'vscode'
4 | import { config, DomainCN, DomainEN } from '../config'
5 | import { User } from './input'
6 | import { writeFileAsync } from '../common/util'
7 | import { signInCommand } from '../commands'
8 |
9 | function parseCookie(cookie: string) {
10 | const keyValueStrArr = cookie.split(';')
11 | const map = {}
12 | keyValueStrArr.forEach((kvStr) => {
13 | let [key, value] = kvStr.split('=')
14 | key = key.trim()
15 | value = value.trim()
16 | map[key] = value
17 | })
18 | return map
19 | }
20 |
21 | async function saveCookie(cookie: string) {
22 | const cookiePath = config.cookiePath
23 | const map = parseCookie(cookie)
24 | const headers = { cookie }
25 | if (map['csrftoken']) {
26 | headers['x-csrftoken'] = map['csrftoken']
27 | }
28 | await writeFileAsync(cookiePath, JSON.stringify(headers))
29 | }
30 | // set-cookie
31 | async function saveResCookie(cookies: string[] = []) {
32 | const map = {}
33 | for (let i = 0; i < cookies.length; i++) {
34 | const cookie = cookies[i]
35 | const arr = cookie.split(';')
36 | const kv = arr[0]
37 | let [key, value] = kv.split('=')
38 | key = key.trim()
39 | if (key) {
40 | map[key] = value
41 | }
42 | }
43 | const cookieStr = Object.keys(map)
44 | .map((key) => `${key}=${map[key]}`)
45 | .join('; ')
46 | const headers = { cookie: cookieStr }
47 | if (map['csrftoken']) {
48 | headers['x-csrftoken'] = map['csrftoken']
49 | }
50 | const cookiePath = config.cookiePath
51 | await writeFileAsync(cookiePath, JSON.stringify(headers))
52 | }
53 | function parseFormData(html: string) {
54 | const formRegExp = //
55 | const r = formRegExp.exec(html.replace(/\n/g, ''))
56 | if (!r) {
57 | return Promise.reject({
58 | message: 'the page of login github not found',
59 | code: 3,
60 | })
61 | }
62 | const formStr = r[0]
63 | const inputRegExp = /]*name="(.*?)"(?:[^>](?!alue))*(?:value="(.*?)")?[^>]*>/g
64 | let inputRegExpRes: RegExpExecArray | null = null
65 | let d = {}
66 | const defaultObj = {
67 | 'webauthn-support': 'supported',
68 | 'webauthn-iuvpaa-support': 'unsupported',
69 | }
70 | while ((inputRegExpRes = inputRegExp.exec(formStr))) {
71 | const name = inputRegExpRes[1]
72 | const value = inputRegExpRes[2]
73 | if (name) {
74 | d[name] = value || ''
75 | }
76 | }
77 | d = Object.assign(d, defaultObj)
78 | return d
79 | }
80 | type RequestAsync = (
81 | arg1: (_request.UriOptions & _request.CoreOptions) | (_request.UrlOptions & _request.CoreOptions)
82 | ) => Promise<_request.Response>
83 | async function githubAuth(html: string, request: RequestAsync, user) {
84 | let data = parseFormData(html)
85 | data = Object.assign(data, {
86 | login: user.name,
87 | password: user.password,
88 | })
89 | // github login and then redirect back
90 | const res = await request({
91 | url: 'https://github.com/session',
92 | form: data,
93 | method: 'POST',
94 | followAllRedirects: true,
95 | headers: {
96 | 'content-type': 'application/x-www-form-urlencoded',
97 | },
98 | })
99 | let body = res.body
100 | if (/Incorrect username or password/.test(body)) {
101 | return Promise.reject({
102 | message: 'Incorrect username or password',
103 | code: 3,
104 | })
105 | }
106 | // enable two-factor
107 | const twoFactorRegExp = /Two-factor authentication/
108 | if (twoFactorRegExp.test(body)) {
109 | body = await githubTwoFactorAuth(body, request)
110 | }
111 | return body
112 | }
113 | async function githubTwoFactorAuth(html: string, request: RequestAsync) {
114 | const authenticityTokenTwoFactor = html.match(/name="authenticity_token" value="(.*?)"/)
115 | if (authenticityTokenTwoFactor === null) {
116 | return Promise.reject({
117 | message: 'get GitHub two-factor page fail',
118 | code: 3,
119 | })
120 | }
121 | const code = await window.showInputBox({
122 | prompt: 'input the Authentication code',
123 | })
124 | if (!code) {
125 | return Promise.reject({
126 | message: 'cancel login',
127 | code: 2,
128 | })
129 | }
130 | const data = {
131 | otp: code,
132 | authenticity_token: authenticityTokenTwoFactor[1],
133 | }
134 | const res = await request({
135 | url: 'https://github.com/sessions/two-factor',
136 | form: data,
137 | method: 'POST',
138 | followAllRedirects: true,
139 | headers: {
140 | 'content-type': 'application/x-www-form-urlencoded',
141 | },
142 | })
143 | return res.body
144 | }
145 | async function githubRedirectBack(body: string, request: RequestAsync) {
146 | const redirectRegExp = / 0 {
40 | node := queue[0]
41 | queue = queue[1:]
42 | if len(arr) == 0 {
43 | return root
44 | }
45 |
46 | leftVal := arr[0]
47 | arr = arr[1:]
48 | if leftVal != "null" {
49 | val, _ := strconv.Atoi(leftVal)
50 | left := &TreeNode{Val: val}
51 | node.Left = left
52 | queue = append(queue, left)
53 | }
54 | if len(arr) == 0 {
55 | return root
56 | }
57 | rightVal := arr[0]
58 | arr = arr[1:]
59 | if rightVal != "null" {
60 | val, _ := strconv.Atoi(rightVal)
61 | right := &TreeNode{Val: val}
62 | node.Right = right
63 | queue = append(queue, right)
64 | }
65 |
66 | }
67 | return root
68 |
69 | }
70 | func deserializeTreeNodeArr(data string) []*TreeNode {
71 | length := len(data)
72 | if length <= 4 {
73 | return []*TreeNode{}
74 | }
75 | str := data[1 : length-1]
76 | r := []*TreeNode{}
77 | for i := 0; i < len(str); i++ {
78 | if str[i:i+1] == "[" {
79 | flag := false
80 | j := i + 1
81 | for ; j < len(str); j++ {
82 | if str[j:j+1] == "]" {
83 | r = append(r, deserializeTreeNode(str[i:j+1]))
84 | flag = true
85 | break
86 | }
87 | }
88 | if !flag {
89 | fmt.Print("parse error")
90 | return []*TreeNode{}
91 | }
92 | i = j
93 | }
94 | }
95 | return r
96 |
97 | }
98 | func serializeTreeNode(root *TreeNode) string {
99 | if root == nil {
100 | return "[]"
101 | }
102 | var arr []string
103 | queue := []*TreeNode{root}
104 | for len(queue) > 0 {
105 | node := queue[0]
106 | queue = queue[1:]
107 | if node != nil {
108 | arr = append(arr, strconv.Itoa(node.Val))
109 | queue = append(queue, node.Left)
110 | queue = append(queue, node.Right)
111 | } else {
112 | arr = append(arr, "null")
113 | }
114 |
115 | }
116 | var i = len(arr) - 1
117 | for arr[i] == "null" {
118 | i--
119 | }
120 | arr = arr[0 : i+1]
121 | return "[" + strings.Join(arr, ",") + "]"
122 | }
123 |
124 | func serializeTreeNodeArr(arr []*TreeNode) string {
125 | strArr := "["
126 | for i := 0; i < len(arr); i++ {
127 | strArr += serializeTreeNode(arr[i])
128 | if i != len(arr)-1 {
129 | strArr += ","
130 | }
131 | }
132 | strArr += "]"
133 | return strArr
134 | }
135 |
136 | func deserializeListNode(data string) *ListNode {
137 | length := len(data)
138 | if length <= 2 {
139 | return nil
140 | }
141 | arr := strings.Split(data[1:length-1], ",")
142 |
143 | c := arr[0]
144 | arr = arr[1:]
145 | val, _ := strconv.Atoi(c)
146 | root := &ListNode{Val: val}
147 | p := root
148 | for len(arr) > 0 {
149 |
150 | c := arr[0]
151 | arr = arr[1:]
152 | val, _ := strconv.Atoi(c)
153 | node := &ListNode{Val: val}
154 | p.Next = node
155 | p = node
156 | }
157 | return root
158 | }
159 |
160 | func deserializeListNodeArr(data string) []*ListNode {
161 | length := len(data)
162 | if length <= 4 {
163 | return []*ListNode{}
164 | }
165 | str := data[1 : length-1]
166 | r := []*ListNode{}
167 | for i := 0; i < len(str); i++ {
168 | if str[i:i+1] == "[" {
169 | flag := false
170 | j := i + 1
171 | for ; j < len(str); j++ {
172 | if str[j:j+1] == "]" {
173 | r = append(r, deserializeListNode(str[i:j+1]))
174 | flag = true
175 | break
176 | }
177 | }
178 | if !flag {
179 | //解析错误
180 | fmt.Print("解析错误")
181 | return []*ListNode{}
182 | }
183 | i = j
184 | }
185 | }
186 | return r
187 | }
188 | func serializeListNode(root *ListNode) string {
189 | var arr []string
190 | p := root
191 | for p != nil {
192 | arr = append(arr, strconv.Itoa(p.Val))
193 | p = p.Next
194 | }
195 | return "[" + strings.Join(arr, ",") + "]"
196 | }
197 | func serializeListNodeArr(arr []*ListNode) string {
198 | newArr := []string{}
199 | for i := 0; i < len(arr); i++ {
200 | newArr = append(newArr, serializeListNode(arr[i]))
201 | }
202 | return "[" + strings.Join(newArr, ",") + "]"
203 | }
204 |
205 | func parseInteger(param string) int {
206 | num, err := strconv.Atoi(param)
207 | if err != nil {
208 | panic(err)
209 | }
210 | return num
211 | }
212 | func parseString(param string) string {
213 | var r string
214 | json.Unmarshal([]byte(param), &r)
215 | return r
216 | }
217 | func parseChar(param string) byte {
218 | r := parseString(param)
219 | return r[0]
220 | }
221 | func parseFloat(param string) float64 {
222 | num, err := strconv.ParseFloat(param, 64)
223 | if err != nil {
224 | panic(err)
225 | }
226 | return num
227 | }
228 | func parseIntegerArr(param string) []int {
229 | var r []int
230 | json.Unmarshal([]byte(param), &r)
231 | return r
232 | }
233 | func parseStringArr(param string) []string {
234 | var r []string
235 | json.Unmarshal([]byte(param), &r)
236 | return r
237 | }
238 | func parseCharArr(param string) []byte {
239 | strArr := parseStringArr(param)
240 | var r []byte
241 | for i := 0; i < len(strArr); i++ {
242 | r = append(r, strArr[i][0])
243 | }
244 | return r
245 | }
246 | func parseIntegerArrArr(param string) [][]int {
247 | var r [][]int
248 | json.Unmarshal([]byte(param), &r)
249 | return r
250 | }
251 |
252 | func parseStringArrArr(param string) [][]string {
253 | var r [][]string
254 | json.Unmarshal([]byte(param), &r)
255 | return r
256 | }
257 | func parseCharArrArr(param string) [][]byte {
258 | strArrArr := parseStringArrArr(param)
259 | var r [][]byte
260 | for i := 0; i < len(strArrArr); i++ {
261 | var item []byte
262 | for j := 0; j < len(strArrArr[i]); j++ {
263 | item = append(item, strArrArr[i][j][0])
264 | }
265 | r = append(r, item)
266 | }
267 | return r
268 | }
269 | func serializeFloat(a float64) string {
270 | return strconv.FormatFloat(a, 'f', 5, 64)
271 | }
272 | func serializeChar(a byte) string {
273 | return serializeInterface(string(a))
274 | }
275 | func serializeCharArr(a []byte) string {
276 | var strArr []string
277 | for i := 0; i < len(a); i++ {
278 | strArr = append(strArr, string(a[i]))
279 | }
280 | return serializeInterface(strArr)
281 | }
282 | func serializeCharArrArr(a [][]byte) string {
283 | var strArr [][]string
284 | for i := 0; i < len(a); i++ {
285 | var item []string
286 | for j := 0; j < len(a[i]); j++ {
287 | item = append(item, string(a[i][j]))
288 | }
289 | strArr = append(strArr, item)
290 | }
291 | return serializeInterface(strArr)
292 | }
293 |
--------------------------------------------------------------------------------
/src/webview/submitHistory.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as vscode from 'vscode'
3 | import { api } from '../api/index'
4 | import { config } from '../config'
5 | import { getHistory, getRemoteSubmits, updateComment } from '../history'
6 | import { highlightCode } from '../util'
7 |
8 | class BuildSubmitHistoryPanel {
9 | /**
10 | * Track the currently panel. Only allow a single panel to exist at a time.
11 | */
12 | public static currentPanel: BuildSubmitHistoryPanel | undefined
13 | public static readonly viewType = 'submitHistoryView'
14 |
15 | private readonly _panel: vscode.WebviewPanel
16 | private readonly _extensionPath: string
17 | private context: vscode.ExtensionContext
18 | public static _text: string
19 | private _disposables: vscode.Disposable[] = []
20 | public static commands = new Map()
21 | private question_id: string
22 |
23 | public static createOrShow(context: vscode.ExtensionContext, question_id: string) {
24 | const column = vscode.ViewColumn.Two
25 | const extensionPath = context.extensionPath
26 | // If we already have a panel, show it.
27 |
28 | if (BuildSubmitHistoryPanel.currentPanel) {
29 | BuildSubmitHistoryPanel.currentPanel._panel.reveal(column)
30 | BuildSubmitHistoryPanel.currentPanel.update(question_id)
31 | return
32 | }
33 |
34 | // Otherwise, create a new panel.
35 | const panel = vscode.window.createWebviewPanel(
36 | BuildSubmitHistoryPanel.viewType,
37 | 'history',
38 | vscode.ViewColumn.Two,
39 | {
40 | // Enable javascript in the webview
41 | enableScripts: true,
42 | localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'media'))],
43 | }
44 | )
45 |
46 | BuildSubmitHistoryPanel.currentPanel = new BuildSubmitHistoryPanel(panel, context, question_id)
47 | }
48 |
49 | private constructor(panel: vscode.WebviewPanel, context: vscode.ExtensionContext, question_id: string) {
50 | this._panel = panel
51 | this._extensionPath = context.extensionPath
52 | this.context = context
53 | this.question_id = question_id
54 | this.initView()
55 | this._update()
56 | // Listen for when the panel is disposed
57 | // This happens when the user closes the panel or when the panel is closed programatically
58 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables)
59 | this._panel.webview.onDidReceiveMessage(
60 | (message) => {
61 | switch (message.command) {
62 | case 'getSubmissionCode':
63 | api.fetchSubmissionDetail({ id: message.id })
64 | .then((code) => {
65 | const p = highlightCode(code, message.lang)
66 | console.log(p)
67 | this._panel.webview.postMessage({
68 | command: 'submissionDetail',
69 | data: {
70 | code: highlightCode(code, message.lang),
71 | id: message.id,
72 | uuid: message.uuid,
73 | },
74 | })
75 | })
76 | .catch((err) => {
77 | this._panel.webview.postMessage({
78 | command: 'submissionDetail',
79 | data: {
80 | code: err,
81 | id: message.id,
82 | uuid: message.uuid,
83 | },
84 | })
85 | })
86 | return
87 | case 'updateComment': {
88 | updateComment(message.type, message.params)
89 | .then(() => {
90 | this._panel.webview.postMessage({
91 | command: 'updateComment',
92 | data: {
93 | code: 200,
94 | msg: 'ok',
95 | uuid: message.uuid,
96 | },
97 | })
98 | })
99 | .catch((err) => {
100 | this._panel.webview.postMessage({
101 | command: 'updateComment',
102 | data: {
103 | code: 201,
104 | msg: 'update fail',
105 | uuid: message.uuid,
106 | },
107 | })
108 | config.log.appendLine(err)
109 | })
110 | }
111 | }
112 | },
113 | null,
114 | this._disposables
115 | )
116 | }
117 |
118 | public dispose() {
119 | BuildSubmitHistoryPanel.currentPanel = undefined
120 |
121 | // Clean up our resources
122 | this._panel.dispose()
123 | while (this._disposables.length) {
124 | const x = this._disposables.pop()
125 | if (x) {
126 | x.dispose()
127 | }
128 | }
129 | }
130 | public update(question_id: string) {
131 | if (question_id !== this.question_id) {
132 | this.question_id = question_id
133 | // BuildSubmitHistoryPanel._text = text;
134 |
135 | this._update()
136 | }
137 | }
138 | private getWebviewUri(name: string) {
139 | const webview = this._panel.webview
140 | const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'media', name))
141 | return webview.asWebviewUri(scriptPathOnDisk)
142 | }
143 | private initView() {
144 | const nonce = getNonce()
145 |
146 | const historyUri = this.getWebviewUri('history.js')
147 | const scriptUri = this.getWebviewUri('highlight.min.js')
148 | const cssUri = this.getWebviewUri('highlight.css')
149 | const historyCssUri = this.getWebviewUri('history.css')
150 | this._panel.webview.html = `
151 |
152 |
153 |
154 |
158 |
159 |
160 | Cat Coding
161 |
162 |
163 |
164 |
165 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | `
184 | }
185 | private async _update() {
186 | const question_id = this.question_id
187 | const data = await getHistory(question_id, highlightCode)
188 | this._panel.webview.postMessage({ command: 'init', data: data })
189 | getRemoteSubmits(question_id).then((data) => {
190 | this._panel.webview.postMessage({
191 | command: 'remoteStorageData',
192 | data: data,
193 | })
194 | })
195 | }
196 | }
197 |
198 | function getNonce() {
199 | let text = ''
200 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
201 | for (let i = 0; i < 32; i++) {
202 | text += possible.charAt(Math.floor(Math.random() * possible.length))
203 | }
204 | return text
205 | }
206 |
207 | export function createSubmitHistoryPanel(context: vscode.ExtensionContext, question_id: string): void {
208 | BuildSubmitHistoryPanel.createOrShow(context, question_id)
209 | }
210 |
--------------------------------------------------------------------------------
/src/model/common.ts:
--------------------------------------------------------------------------------
1 | export interface GraphqlVariables {
2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
3 | [key: string]: any
4 | }
5 | export interface GraphRes {
6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
7 | errors: any
8 | data: T
9 | }
10 | export enum Lang {
11 | en = 'en',
12 | cn = 'cn',
13 | }
14 | export interface Tag {
15 | slug: string
16 | name: string
17 | questions: number[]
18 | translatedName: string
19 | }
20 | export interface CodeSnippet {
21 | lang: string
22 | langSlug: string
23 | code: string
24 | }
25 | // export type CodeLang = 'C++' | 'Java' | 'Python' | 'Python3' | 'C' | 'C#' | 'JavaScript' | 'Ruby' | 'Swift' | 'Go' | 'Scala' | 'Kotlin' | 'Rust' | 'PHP' | 'TypeScript'
26 | // export const CodeLangs = ['C++', 'Java', 'Python', 'Python3', 'C', 'C#', 'JavaScript', 'Ruby', 'Swift', 'Go', 'Scala', 'Kotlin', 'Rust', 'PHP', 'TypeScript']
27 | export interface ConciseQuestion {
28 | fid: string
29 | level: number
30 | id: number
31 | title: string
32 | slug: string
33 | acs: number
34 | submitted: number
35 | paid_only: boolean
36 | status: string
37 | name: string
38 | }
39 | export interface CheckResponse {
40 | status_code: number
41 | lang: string
42 | run_success: boolean
43 | status_runtime: string
44 | memory: number
45 | question_id: string
46 | elapsed_time: number
47 | compare_result: string
48 | code_output: string
49 | std_output: string
50 | last_testcase: string
51 | task_finish_time: number
52 | task_name: string
53 | finished: boolean
54 | status_msg: string
55 | state: State
56 | fast_submit: boolean
57 | total_correct: number
58 | total_testcases: number
59 | submission_id: string
60 | runtime_percentile: number
61 | status_memory: string
62 | memory_percentile: number
63 | pretty_lang: string
64 | input_formatted?: string
65 | expected_output?: string
66 | }
67 | type State = 'PENDING' | 'STARTED' | 'SUCCESS'
68 | export interface MapIdConciseQuestion {
69 | [id: number]: ConciseQuestion
70 | }
71 | export interface Problems {
72 | user_name: string
73 | num_solved: number
74 | num_total: number
75 | ac_easy: number
76 | ac_medium: number
77 | ac_hard: number
78 | stat_status_pairs: StatStatusPairs[]
79 | frequency_high: number
80 | frequency_mid: number
81 | category_slug: string
82 | }
83 | interface StatStatusPairs {
84 | stat: Stat
85 | status: unknown
86 | difficulty: Difficulty
87 | paid_only: boolean
88 | is_favor: boolean
89 | frequency: number
90 | progress: number
91 | }
92 | interface Difficulty {
93 | level: number
94 | }
95 | interface Stat {
96 | question_id: number
97 | question__title: string
98 | question__title_slug: string
99 | question__hide: boolean
100 | total_acs: number
101 | total_submitted: number
102 | total_column_articles: number
103 | frontend_question_id: string
104 | is_new_question: boolean
105 | }
106 | export enum ErrorStatus {
107 | Unlogin = 403,
108 | InvalidCookie = 499,
109 | }
110 |
111 | export enum AskForImportState {
112 | Yes = 'Yes',
113 | No = 'No',
114 | Later = 'Later',
115 | }
116 |
117 | export enum PendingStatus {
118 | NotPending = 'Not Pending',
119 | Pending = 'Pending',
120 | }
121 | export enum FlagType {
122 | BLUE = 'BLUE',
123 | ORANGE = 'ORANGE',
124 | GREEN = 'GREEN',
125 | PURPLE = 'PURPLE',
126 | RED = 'RED',
127 | }
128 | export interface SubmissionComment {
129 | comment: string
130 | flagType: FlagType
131 | }
132 | export interface Submission {
133 | id: string
134 | isPending: PendingStatus
135 | submissionComment?: SubmissionComment
136 | lang: string
137 | memory: string
138 | runtime: string
139 | statusDisplay: string
140 | timestamp: string
141 | url: string
142 | }
143 |
144 | export interface LocalSubmission {
145 | code: string
146 | submission: Submission
147 | }
148 |
149 | export type LocalSubmissionArr = LocalSubmission[]
150 |
151 | export enum HistoryType {
152 | Answer = 'answer',
153 | LocalSubmit = 'localSubmit',
154 | RemoteSubmit = 'remoteSubmit',
155 | }
156 |
157 | export interface UpdateCommentOption {
158 | id: string
159 | comment: string
160 | questionId: string
161 | }
162 | export type UpdateRemoteCommentOption = Omit
163 |
164 | type Requred = {
165 | [key in R]-?: T[key]
166 | } &
167 | {
168 | [key in keyof T]: T[key]
169 | }
170 | export function checkParams(obj: T, attrs: R[]): asserts obj is Requred {
171 | const verify = attrs.every((attr) => !!obj[attr])
172 | if (!verify) {
173 | const attr = attrs.find((attr) => !obj[attr])!
174 | throw new Error(`options error, ${attr} is ${obj[attr]}`)
175 | }
176 | }
177 |
178 | export interface ContestDetail {
179 | contest: Contest
180 | questions: ContestQuestion[]
181 | user_num: number
182 | has_chosen_contact: boolean
183 | company: Company
184 | registered: boolean
185 | containsPremium: boolean
186 | }
187 | interface Company {
188 | name: string
189 | description: string
190 | logo: string
191 | slug?: string
192 | }
193 | interface ContestQuestion {
194 | id: number
195 | question_id: number
196 | credit: number
197 | title: string
198 | english_title: string
199 | title_slug: string
200 | category_slug: string
201 | }
202 | interface Contest {
203 | id: number
204 | title: string
205 | title_slug: string
206 | description: string
207 | duration: number
208 | start_time: number
209 | is_virtual: boolean
210 | origin_start_time: number
211 | is_private: boolean
212 | related_contest_title?: unknown
213 | discuss_topic_id?: number
214 | }
215 |
216 | export interface CnContestPageData {
217 | questionId: string
218 | questionIdHash: string
219 | questionTitleSlug: string
220 | questionTitle: string
221 | questionSourceTitle: string
222 | questionExampleTestcases: string
223 | categoryTitle: string
224 | contestTitleSlug: string
225 | loginUrl: string
226 | isSignedIn: boolean
227 | sessionId: string
228 | reverseUrl: ReverseUrl
229 | enableRunCode: boolean
230 | enableSubmit: boolean
231 | submitUrl: string
232 | interpretUrl: string
233 | judgeType: string
234 | nextChallengePairs?: unknown
235 | codeDefinition: CodeDefinition[]
236 | enableTestMode: boolean
237 | metaData: MetaData
238 | sampleTestCase: string
239 | judgerAvailable: boolean
240 | envInfo: EnvInfo
241 | questionContent: string
242 | questionSourceContent: string
243 | editorType: string
244 | }
245 | interface EnvInfo {
246 | cpp: string[]
247 | java: string[]
248 | python: string[]
249 | c: string[]
250 | csharp: string[]
251 | javascript: string[]
252 | ruby: string[]
253 | swift: string[]
254 | golang: string[]
255 | python3: string[]
256 | scala: string[]
257 | kotlin: string[]
258 | rust: string[]
259 | php: string[]
260 | typescript: string[]
261 | racket: string[]
262 | }
263 | interface MetaData {
264 | name: string
265 | params: Param[]
266 | return: Return
267 | }
268 | interface Return {
269 | type: string
270 | }
271 | interface Param {
272 | name: string
273 | type: string
274 | }
275 | export interface CodeDefinition {
276 | value: string
277 | text: string
278 | defaultCode: string
279 | }
280 | interface ReverseUrl {
281 | latest_submission: string
282 | account_login: string
283 | maintenance: string
284 | profile: string
285 | }
286 |
287 | export interface CommonQuestion {
288 | translatedContent: string
289 | translatedTitle: string
290 | questionFrontendId: string
291 | metaData: string
292 | content: string
293 | title: string
294 | titleSlug: string
295 | codeSnippets: CodeSnippet[]
296 | questionId: string
297 | questionSourceContent?: string
298 | }
299 |
--------------------------------------------------------------------------------
/src/model/question.ts:
--------------------------------------------------------------------------------
1 | import { CodeSnippet, GraphqlVariables, SubmissionComment } from './common'
2 |
3 | export interface Chapter {
4 | descriptionText: string
5 | id: string
6 | slug: string
7 | title: string
8 | }
9 | export interface ChaptersRes {
10 | chapters: Chapter[]
11 | }
12 | interface ChapterQuestion {
13 | chapterId: number
14 | id: string
15 | isEligibleForCompletion: boolean
16 | paidOnly: boolean
17 | prerequisites: unknown[]
18 | title: string
19 | type: number
20 | }
21 | interface ChapterDetail {
22 | description: string
23 | id: string
24 | slug: string
25 | title: string
26 | items: ChapterQuestion[]
27 | }
28 | export interface ChapterRes {
29 | chapter: ChapterDetail
30 | }
31 | interface GetOrCreateExploreSession {
32 | progress: string
33 | }
34 |
35 | interface DailyProgress {
36 | is_complete: boolean
37 | }
38 | interface DailyQuestionMap {
39 | [key: string]: DailyProgress
40 | }
41 | export interface DailyWeekMap {
42 | [key: string]: DailyQuestionMap
43 | }
44 | export interface ChaptersProgressRes {
45 | getOrCreateExploreSession: GetOrCreateExploreSession
46 | }
47 | interface ChapterItem {
48 | question: Pick
49 | }
50 | export interface ChapterItemRes {
51 | item: ChapterItem
52 | }
53 |
54 | export interface ConciseQuestion {
55 | fid: string
56 | level: number
57 | id: number
58 | title: string
59 | slug: string
60 | acs: number
61 | submitted: number
62 | paid_only: boolean
63 | status: string
64 | name: string
65 | }
66 | export interface MapIdConciseQuestion {
67 | [id: number]: ConciseQuestion
68 | }
69 |
70 | export interface TodayRecordData {
71 | todayRecord: TodayRecord[]
72 | }
73 | interface TodayRecord {
74 | question: Pick
75 | lastSubmission: LastSubmission
76 | date: string
77 | userStatus: UserStatus
78 | __typename: string
79 | }
80 | interface LastSubmission {
81 | id: string
82 | __typename: string
83 | }
84 | type UserStatus = 'FINISH' | 'NOT_START'
85 |
86 | export interface QuestionTranslationData {
87 | translations: Translation[]
88 | }
89 | interface Translation {
90 | questionId: string
91 | title: string
92 | }
93 |
94 | export interface GraphqlResponse {
95 | data: T
96 | }
97 | export interface DailyQuestionRecordData {
98 | dailyQuestionRecords: [DailyQuestionRecord]
99 | }
100 | export interface DailyQuestionRecord {
101 | date: string
102 |
103 | question: Pick
104 | userStatus: string
105 | __typename: string
106 | }
107 |
108 | export interface GraphqlRequestData {
109 | operationName: string | null
110 | query: string
111 | variables: GraphqlVariables
112 | }
113 |
114 | export interface QuestionData {
115 | question: Question
116 | }
117 | export interface Question {
118 | questionId: string
119 | questionFrontendId: string
120 | boundTopicId: number
121 | title: string
122 | titleSlug: string
123 | content: string
124 | translatedTitle: string
125 | translatedContent: string
126 | isPaidOnly: boolean
127 | difficulty: string
128 | likes: number
129 | dislikes: number
130 | isLiked: unknown
131 | similarQuestions: string
132 | contributors: []
133 | langToValidPlayground: string
134 | topicTags: TopicTags[]
135 | companyTagStats: unknown
136 | codeSnippets: CodeSnippet[]
137 | stats: string
138 | hints: string[]
139 | solution: Solution
140 | status: Status
141 | sampleTestCase: string
142 | metaData: string
143 | judgerAvailable: boolean
144 | judgeType: string
145 | mysqlSchemas: string[]
146 | enableRunCode: boolean
147 | envInfo: string
148 | book: unknown
149 | isSubscribed: boolean
150 | isDailyQuestion: boolean
151 | dailyRecordStatus: string
152 | editorType: string
153 | ugcQuestionId: unknown
154 | style: string
155 | __typename: string
156 |
157 | // questionTitleSlug: string
158 | }
159 | interface Solution {
160 | canSeeDetail: boolean
161 | id: string
162 | }
163 | type Status = 'ac' | 'notac' | null
164 |
165 | interface TopicTags {
166 | name: string
167 | slug: string
168 | translatedName: string
169 | questions: number[] //question id
170 | __typename: string
171 | }
172 |
173 | export interface ContestData {
174 | allContests: Pick[]
175 | }
176 | interface Contest {
177 | containsPremium: boolean
178 | title: string
179 | cardImg: string
180 | titleSlug: string
181 | description: string
182 | startTime: number
183 | duration: number
184 | originStartTime: number
185 | isVirtual: boolean
186 | company: Company
187 | __typename: string
188 | }
189 | interface Company {
190 | watermark: string
191 | __typename: string
192 | }
193 |
194 | export interface TagData {
195 | topics: TopicTags[]
196 | }
197 | // interface Topics {
198 | // slug: string
199 | // name: string
200 | // questions: number[]
201 | // translatedName: string
202 | // }
203 |
204 | export interface SubmitOptions {
205 | titleSlug: string
206 | typed_code: string
207 | question_id: string
208 | lang?: string
209 | }
210 | export interface SubmitContestOptions extends SubmitOptions {
211 | weekname: string
212 | }
213 | export interface SubmitResponse {
214 | submission_id: number
215 | }
216 | export interface CheckResponse {
217 | status_code: number
218 | lang: string
219 | run_success: boolean
220 | status_runtime: string
221 | memory: number
222 | question_id: string
223 | elapsed_time: number
224 | compare_result: string
225 | code_output: string
226 | std_output: string
227 | last_testcase: string
228 | task_finish_time: number
229 | task_name: string
230 | finished: boolean
231 | status_msg: string
232 | state: State
233 | fast_submit: boolean
234 | total_correct: number
235 | total_testcases: number
236 | submission_id: string
237 | runtime_percentile: number
238 | status_memory: string
239 | memory_percentile: number
240 | pretty_lang: string
241 | }
242 |
243 | export interface CheckContestResponse {
244 | state: State
245 | }
246 | type State = 'PENDING' | 'STARTED' | 'SUCCESS'
247 |
248 | export interface CheckOptions {
249 | submission_id: number
250 | titleSlug: string
251 | }
252 | export interface CheckContestOptions extends CheckOptions {
253 | weekname: string
254 | }
255 |
256 | export interface SubmissionsResponse {
257 | submissionList: SubmissionList
258 | }
259 | export interface SubmissionsOptions {
260 | lastKey?: string
261 | limit?: number
262 | offset?: number
263 | titleSlug: string
264 | }
265 | interface SubmissionList {
266 | lastKey: string
267 | hasNext: boolean
268 | submissions: Submissions[]
269 | }
270 | export interface Submissions {
271 | id: string
272 | statusDisplay: string
273 | lang: string
274 | runtime: string
275 | timestamp: string
276 | url: string
277 | isPending: string
278 | memory: string
279 | submissionComment?: SubmissionComment
280 | }
281 | export interface SubmissionDetailOptions {
282 | id: string
283 | }
284 |
285 | export interface SubmissionDetailPageData {
286 | submissionData: SubmissionData
287 | questionId: string
288 | sessionId: string
289 | getLangDisplay: string
290 | submissionCode: string
291 | editCodeUrl: string
292 | checkUrl: string
293 | runtimeDistributionFormatted: string
294 | memoryDistributionFormatted: string
295 | langs: unknown[]
296 | runtime: string
297 | memory: string
298 | enableMemoryDistribution: string
299 | nonSufficientMsg: string
300 | }
301 | interface SubmissionData {
302 | status_code: number
303 | runtime: string
304 | memory: string
305 | total_correct: string
306 | total_testcases: string
307 | compare_result: string
308 | input_formatted: string
309 | input: string
310 | expected_output: string
311 | code_output: string
312 | last_testcase: string
313 | }
314 |
--------------------------------------------------------------------------------
/src/common/parseContent.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'pretty-object-string'
2 | import { escape2html } from './util'
3 | const isSpace = (c) => /\s/.test(c)
4 | const isWord = (c) => /\w/.test(c)
5 | function delComment(src) {
6 | const commentRegExp = /(\/\*([\s\S]*?)\*\/|('.*?')|(".*?")|\/\/(.*)$)/gm
7 | return src.replace(commentRegExp, commentReplace)
8 | }
9 | function commentReplace(_match, _multi, _multiText, singlePrefix, double) {
10 | return singlePrefix || double || ''
11 | }
12 | interface Demo {
13 | input: Input[]
14 | output: string
15 | }
16 | enum Kind {
17 | scanContinue,
18 | scanBeginTag,
19 | scanTagText,
20 | scanEndTag,
21 | scanWord,
22 | scanSpace,
23 | scanInputFlag,
24 | Identify,
25 | scanEqual,
26 | scanParam,
27 | scanResult,
28 | }
29 | interface Input {
30 | key?: string
31 | value?: string
32 | }
33 | //TODO refactor
34 | export class ParseContent {
35 | public step: (char: string, next: string, i: number) => Kind = this.stateBegin
36 | public prevStep: (char: string, next?: string) => Kind = this.stateBegin
37 | public word = ''
38 | public words: string[] = []
39 | public tagStatus = 0
40 | public exampleState = 0
41 | public demos: Demo[] = []
42 | constructor(public readonly content: string) {
43 | this.content = delComment(escape2html(content.replace(/<[^>]*>/g, '')))
44 | this.init()
45 | }
46 | init() {
47 | this.step = this.stateBegin
48 | let input: Input[] = []
49 | const linkReg = /^\s*-?\d+->/
50 | const demos: Demo[] = []
51 | let identify
52 | let i = 0
53 | try {
54 | for (; i < this.content.length; i++) {
55 | const c = this.content[i]
56 | const n = this.content[i + 1]
57 |
58 | const out = this.step(c, n, i)
59 | if (out === Kind.Identify) {
60 | identify = this.word
61 | this.word = ''
62 | } else if (out === Kind.scanParam) {
63 | let value = this.content.slice(i, i + 10)
64 | let index, output
65 | if (linkReg.test(value)) {
66 | const result = this.parseLink(i)
67 | index = result.index
68 | output = result.output
69 | } else {
70 | value = this.content.slice(i)
71 | const result = parse(value, {
72 | partialIndex: true,
73 | compress: true,
74 | })
75 | index = result.index
76 | output = result.output
77 | }
78 | input.push({
79 | key: identify,
80 | value: output,
81 | })
82 |
83 | i = i + index - 1
84 | this.step = this.stateInputIdentity2
85 | } else if (out === Kind.scanResult) {
86 | let value = this.content.slice(i, i + 10)
87 | let index, output
88 | if (linkReg.test(value)) {
89 | const result = this.parseLink(i)
90 | index = result.index
91 | output = result.output
92 | } else {
93 | value = this.content.slice(i)
94 | const result = parse(value, {
95 | partialIndex: true,
96 | compress: true,
97 | })
98 | index = result.index
99 | output = result.output
100 | }
101 | i = i + index - 1
102 | demos.push({
103 | input,
104 | output: output,
105 | })
106 | input = []
107 | this.word = ''
108 | identify = ''
109 | this.step = this.stateWord
110 | }
111 | }
112 | } catch (err) {
113 | console.log('content', this.content)
114 | console.log(this.content.slice(i - 20, i))
115 | console.log(err)
116 | }
117 | this.demos = demos
118 | }
119 | static getTestCases(content: string) {
120 | const p = new ParseContent(content)
121 | return p.demos
122 | }
123 | parseLink(index) {
124 | let numStr = ''
125 | const output: number[] = []
126 | const start = index
127 |
128 | while (index < this.content.length && isSpace(this.content[index])) {
129 | index++
130 | }
131 | while (index < this.content.length && /[\d->N]/.test(this.content[index])) {
132 | const char = this.content[index]
133 | if (/\d/.test(char)) {
134 | numStr += char
135 | } else if (char === '-') {
136 | const nextChar = this.content[index + 1]
137 | if (nextChar === '>') {
138 | output.push(parseInt(numStr))
139 | numStr = ''
140 | } else if (/\d/.test(nextChar)) {
141 | numStr = char
142 | }
143 | } else if (char === 'N') {
144 | if (this.content.slice(index, index + 4) !== 'NULL') {
145 | throw new Error('parse link error')
146 | }
147 | return {
148 | index: index + 4 - start,
149 | output: JSON.stringify(output),
150 | }
151 | }
152 | index++
153 | }
154 | output.push(parseInt(numStr))
155 | return {
156 | index: index - start,
157 | output: JSON.stringify(output),
158 | }
159 | }
160 | lineEndAt(position) {
161 | let i = position
162 | while (this.content[i++]) {
163 | if (this.content[i] === '\n') {
164 | return i
165 | }
166 | }
167 | return i
168 | }
169 | stateBegin(char: string): Kind {
170 | if (isSpace(char)) {
171 | return Kind.scanContinue
172 | }
173 |
174 | this.step = this.stateWord
175 | return this.stateWord(char)
176 | }
177 | stateWord(char: string): Kind {
178 | if (isSpace(char)) {
179 | this.word = ''
180 | return Kind.scanSpace
181 | }
182 | this.word += char
183 | if (this.exampleState === 1 && this.word === 'Input:') {
184 | this.word = ''
185 | this.exampleState = 0
186 | this.step = this.stateInputIdentityOrValue
187 | } else if (this.word === 'Example') {
188 | this.exampleState = 1
189 | }
190 | return Kind.scanWord
191 | }
192 | stateInputIdentityOrValue(char: string, n: string, i: number): Kind {
193 | if (isSpace(char)) {
194 | return Kind.scanSpace
195 | }
196 | if (!/[a-zA-Z_]/.test(char) || /(true|false|null)/.test(this.content.slice(i, i + 6))) {
197 | return Kind.scanParam
198 | }
199 | if (this.content.slice(i, i + 7) === 'Output:') {
200 | //Compatibility Special Conditions id:53
201 | /**
202 | * content Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.
203 |
204 | Example:
205 |
206 |
207 | Input: [-2,1,-3,4,-1,2,1,-5,4],
208 | Output: 6
209 | Explanation: [4,-1,2,1] has the largest sum = 6.
210 |
211 | */
212 | this.step = this.stateOut
213 | return this.step(char, n, i)
214 | }
215 | this.step = this.stateInputIdentity
216 | return this.step(char, n, i)
217 | }
218 | stateInputIdentity(char: string, _n: string, _i: number): Kind {
219 | if (isSpace(char)) {
220 | if (!this.word) {
221 | return Kind.scanSpace
222 | }
223 | this.step = this.stateEqual
224 | return Kind.Identify
225 | }
226 | if (!isWord(char)) {
227 | throw new Error('input identity invalid')
228 | }
229 | this.word += char
230 | return Kind.scanWord
231 | }
232 | stateEqual(char: string): Kind {
233 | if (isSpace(char)) {
234 | return Kind.scanSpace
235 | }
236 | if (char === '=') {
237 | this.step = this.stateParam
238 | return Kind.scanEqual
239 | }
240 | throw new Error('parse equal error')
241 | }
242 | stateParam(char: string): Kind {
243 | if (isSpace(char)) {
244 | return Kind.scanSpace
245 | }
246 | return Kind.scanParam
247 | }
248 | stateInputIdentity2(char: string, n: string, i: number): Kind {
249 | if (isSpace(char)) {
250 | return Kind.scanSpace
251 | }
252 | if (char === ',') {
253 | this.step = this.stateInputIdentityOrValue
254 | return Kind.scanContinue
255 | }
256 | this.step = this.stateOut
257 | return this.step(char, n, i)
258 | }
259 | stateOut(char: string): Kind {
260 | if (isSpace(char)) {
261 | return Kind.scanSpace
262 | }
263 | this.word += char
264 | if (this.word === 'Output:') {
265 | this.word = ''
266 | this.step = this.stateResult
267 | return Kind.scanContinue
268 | }
269 | return Kind.scanContinue
270 | }
271 | stateResult(char: string): Kind {
272 | this.word += char
273 | return Kind.scanResult
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/fileStorage/db.ts:
--------------------------------------------------------------------------------
1 | import path = require('path')
2 | import fs = require('fs')
3 | import util = require('util')
4 | import { crc32, md5 } from './util'
5 | import { existDir, existFile, writeFileAsync } from '../common/util'
6 | const openAsync = util.promisify(fs.open)
7 | const readAsync = util.promisify(fs.read)
8 | const writeAsync = util.promisify(fs.write)
9 | const readFileAsync = util.promisify(fs.readFile)
10 |
11 | const META = 'Meta'
12 | const MAGIC = 0x125e591
13 | const VERSION = 1
14 | const HeaderConfig: Field[] = [
15 | {
16 | field: 'magic',
17 | type: 'int',
18 | len: 32,
19 | },
20 | {
21 | field: 'version',
22 | type: 'int',
23 | len: 32,
24 | },
25 | {
26 | field: 'checksum',
27 | type: 'int',
28 | len: 32,
29 | },
30 | {
31 | field: 'count',
32 | type: 'int',
33 | len: 32,
34 | },
35 | ]
36 | const SectionHeaderConfig: Field[] = [
37 | {
38 | field: 'checksum',
39 | type: 'int',
40 | len: 32,
41 | },
42 | {
43 | field: 'id',
44 | type: 'int',
45 | len: 32,
46 | },
47 | {
48 | field: 'fid',
49 | type: 'int',
50 | len: 32,
51 | },
52 | {
53 | field: 'offset',
54 | type: 'int',
55 | len: 32,
56 | },
57 | {
58 | field: 'size',
59 | type: 'int',
60 | len: 16,
61 | },
62 | {
63 | field: 'hash',
64 | type: 'hex',
65 | len: 128,
66 | },
67 | {
68 | field: 'timestamp',
69 | type: 'int',
70 | len: 64,
71 | },
72 | ]
73 | interface DbOptions {
74 | dbDir: string
75 | filename: string
76 | }
77 | export default class Db {
78 | private dbDir: string
79 | private filename: string
80 | private headerPath: string
81 | private filePath: string
82 | public fds: number[] = [-1, -1]
83 | public buffer: Buffer = Buffer.alloc(0)
84 | public header: Header | null = null
85 | public sectionHeaders: SectionHeader[] = []
86 | // public headerFd: number
87 | constructor(options: DbOptions) {
88 | this.dbDir = options.dbDir
89 | this.filename = options.filename
90 | this.headerPath = path.join(this.dbDir, this.filename + META)
91 | this.filePath = path.join(this.dbDir, this.filename)
92 | }
93 | async init(): Promise {
94 | await existDir(this.dbDir)
95 | await existFile(this.headerPath)
96 | await existFile(this.filePath)
97 | const headerFd = await openAsync(this.headerPath, 'r+')
98 | const dataFd = await openAsync(this.filePath, 'r+')
99 | this.fds = [headerFd, dataFd]
100 |
101 | const buffer = await readFileAsync(this.headerPath)
102 | this.buffer = buffer
103 | await this._init()
104 | }
105 | async _init(): Promise {
106 | const buffer = this.buffer
107 | let header: Header
108 | const sectionHeaders: SectionHeader[] = []
109 | if (!buffer.length) {
110 | const count = 0
111 | header = {
112 | magic: MAGIC,
113 | version: VERSION,
114 | checksum: crc32(Buffer.alloc(4)),
115 | count,
116 | }
117 | await writeFileAsync(this.headerPath, convertToBuffer(header, HeaderConfig))
118 | } else {
119 | const headerLen = getLen(HeaderConfig)
120 | const headerBuf = buffer.slice(0, headerLen)
121 | header = convertToObj(headerBuf, HeaderConfig)
122 |
123 | // header = {
124 | // magic: buffer.slice(0, 4).readInt32BE(),
125 | // version: buffer.slice(4, 8).readInt32BE(),
126 | // checksum: buffer.slice(8, 12).readInt32BE(),
127 | // count: buffer.slice(12, 16).readInt32BE()
128 | // }
129 | const sectionHeadersBuf = buffer.slice(headerLen)
130 | const len = getLen(SectionHeaderConfig)
131 | for (let i = 0; i < sectionHeadersBuf.length / len; i++) {
132 | const buf = sectionHeadersBuf.slice(i * len, (i + 1) * len)
133 |
134 | sectionHeaders.push(convertToObj(buf, SectionHeaderConfig))
135 | }
136 | }
137 | this.header = header
138 | this.sectionHeaders = sectionHeaders
139 | }
140 | async read(id: number): Promise {
141 | const sectionHeader = this.sectionHeaders.find((v) => v.id === id)
142 | if (sectionHeader) {
143 | const buf = Buffer.alloc(sectionHeader.size)
144 | await readAsync(this.fds[1], buf, 0, buf.length, sectionHeader.offset)
145 | return buf.toString('utf8')
146 | }
147 | return ''
148 | }
149 | async add(question: string, id: number, fid: number): Promise {
150 | if (this.sectionHeaders.find((v) => v.id === id)) {
151 | return
152 | }
153 | const buffer = Buffer.from(question)
154 | const count = (this.header as Header).count
155 | const last = count > 0 ? this.sectionHeaders[count - 1] : { offset: 0, size: 0 }
156 | const offset = last.offset + last.size
157 | const size = buffer.length
158 | const hash = md5(buffer)
159 | const partialSecHeader = {
160 | id,
161 | fid,
162 | offset,
163 | size,
164 | hash,
165 | timestamp: Date.now(),
166 | }
167 | const config = SectionHeaderConfig.slice(1)
168 | const partialSecHeaderBuf = convertToBuffer(partialSecHeader, config)
169 | const checksum = crc32(partialSecHeaderBuf)
170 | const sectionHeader = {
171 | checksum,
172 | ...partialSecHeader,
173 | }
174 | this.sectionHeaders[count] = sectionHeader
175 |
176 | const secHeaderBuf = convertToBuffer(sectionHeader, SectionHeaderConfig)
177 |
178 | ;(this.header as Header).count++
179 | ;(this.header as Header).checksum = crc32(convertToBuffer(this.sectionHeaders, SectionHeaderConfig))
180 | const headerBuf = convertToBuffer(this.header, HeaderConfig)
181 | const headerLen = getLen(HeaderConfig)
182 | const secHeaderLen = getLen(SectionHeaderConfig)
183 | const secOffset = headerLen + count * secHeaderLen
184 | await Promise.all([
185 | writeAsync(this.fds[0], secHeaderBuf, 0, secHeaderBuf.length, secOffset),
186 | writeAsync(this.fds[0], headerBuf, 0, headerBuf.length, 0),
187 | writeAsync(this.fds[1], buffer, 0, buffer.length, offset),
188 | ])
189 | }
190 | }
191 | interface Header {
192 | magic: number
193 | version: number
194 | checksum: number
195 | count: number
196 | }
197 | interface SectionHeader {
198 | checksum: number
199 | id: number
200 | fid: number
201 | offset: number
202 | size: number
203 | hash: string
204 | timestamp: number
205 | }
206 | type FieldType = 'int' | 'hex'
207 | export interface Field {
208 | field: string
209 | type: FieldType
210 | len: number
211 | }
212 |
213 | export function convertToObj(buffer: Buffer, config: Field[]): T {
214 | let i = 0
215 | const obj: Partial = {}
216 | for (const field of config) {
217 | obj[field.field] = read(buffer, i, field)
218 | i += field.len / 8
219 | }
220 | return obj as T
221 | }
222 | export function convertToBuffer(obj: T[] | T, config: Field[]): Buffer {
223 | if (Array.isArray(obj)) {
224 | return Buffer.concat(obj.map((v) => convertToBuffer(v, config)))
225 | }
226 | let i = 0
227 | const len = getLen(config)
228 | const buf = Buffer.alloc(len)
229 |
230 | for (const field of config) {
231 | write(obj[field.field], buf, i, field)
232 | i += field.len / 8
233 | }
234 | return buf
235 | }
236 |
237 | function getLen(config: Field[]) {
238 | return config.reduce((prev, cur) => prev + cur.len / 8, 0)
239 | }
240 |
241 | function read(buffer: Buffer, offset: number, field: Field) {
242 | const mapFun = {
243 | int: (buffer: Buffer) => {
244 | const byteLen = field.len / 8
245 | if (byteLen === 8) {
246 | try {
247 | const a = Number(buffer.readBigUInt64BE(offset))
248 | return a
249 | } catch (err) {
250 | console.log(buffer)
251 | }
252 | } else {
253 | return buffer.readUIntBE(offset, field.len / 8)
254 | }
255 | },
256 |
257 | hex: (buffer: Buffer) => buffer.slice(offset, offset + field.len / 8).toString('hex'),
258 | }
259 | const fun = mapFun[field.type]
260 | return fun(buffer)
261 | }
262 | function write(value: number | string, buffer: Buffer, offset: number, field: Field) {
263 | const mapFunc = {
264 | int: () => {
265 | const byteLen = field.len / 8
266 | if (byteLen === 8) {
267 | buffer.writeBigInt64BE(BigInt(value as number), offset)
268 | } else {
269 | buffer.writeUIntBE(value as number, offset, field.len / 8)
270 | }
271 | },
272 | hex: () => Buffer.from(value as string, 'hex').copy(buffer, offset),
273 | }
274 | const fun = mapFunc[field.type]
275 | return fun()
276 | }
277 |
--------------------------------------------------------------------------------