├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ └── feature-request.md
├── .gitignore
├── .travis.yml
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── STATUS.md
├── jest.config.js
├── package.json
├── resources
├── dark
│ ├── login-dark.png
│ ├── refresh-dark.png
│ └── search-dark.png
├── img
│ ├── favicon.ico
│ ├── luogu-normal.png
│ ├── luogu-small.png
│ ├── luogu-very-small.png
│ └── luogu.png
└── light
│ ├── login-light.png
│ ├── refresh-light.png
│ └── search-light.png
├── rollup.config.js
├── src
├── commands
│ ├── SuperCommand.ts
│ ├── about
│ │ └── index.ts
│ ├── index.ts
│ ├── login
│ │ └── index.ts
│ ├── logout
│ │ └── index.ts
│ ├── search
│ │ └── index.ts
│ └── submit
│ │ └── index.ts
├── extension.ts
├── model
│ └── Problem.ts
├── store
│ ├── index.ts
│ ├── shared.ts
│ └── userManager.ts
├── utils
│ ├── api.ts
│ ├── debug.ts
│ ├── shared.ts
│ ├── uiUtils.ts
│ └── userManager.ts
└── views
│ ├── index.ts
│ ├── luoguChannel.ts
│ └── luoguStatusBar.ts
├── test.sh
├── tests
└── index.test.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behavior to automatically normalize line endings.
2 | * text=auto
3 |
4 | *.ts filter=shared
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | ** write your advice here**
8 |
9 | xxx xxxxx xxxx xxx.
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | package-lock.json
6 | *.vsix
7 | .idea
8 | .*.*
9 | .*
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "8"
5 | os:
6 | - osx
7 | - linux
8 | before_install:
9 | - if [ $TRAVIS_OS_NAME == "linux" ]; then
10 | export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0;
11 | sh -e /etc/init.d/xvfb start;
12 | sleep 3;
13 | fi
14 | before_script:
15 | - npm install
16 | - npm run vscode:prepublish
17 | script:
18 | - npm run compile
19 | - npm run test --silent
20 |
--------------------------------------------------------------------------------
/.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 | "ms-vscode.vscode-typescript-tslint-plugin"
6 | ]
7 | }
--------------------------------------------------------------------------------
/.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 | "name": "Run Extension",
9 | "type": "extensionHost",
10 | "request": "launch",
11 | "runtimeExecutable": "${execPath}",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "env": {
16 | "NODE_ENV": "development"
17 | },
18 | "outFiles": [
19 | "${workspaceFolder}/dist/**/*.js"
20 | ],
21 | "preLaunchTask": "npm: compile"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/.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 | }
21 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | dist/test/**
4 | dist/**/*.map
5 | src/**
6 | .gitignore
7 | tsconfig.json
8 | vsc-extension-quickstart.md
9 | tslint.json
10 | .idea
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 0.1.13
4 |
5 | - Support C#/VB
6 |
7 | ## 0.1.12
8 |
9 | - Support C++17/GO/Ruby/PHP7/Rust
10 |
11 | ## 0.1.11
12 |
13 | - Fix Cann't submit the solution and other bugs
14 |
15 | ## 0.1.10
16 |
17 | - Support C++14
18 |
19 | - Fix bugs
20 |
21 | ## 0.1.9
22 |
23 | - Add info message when the token expired
24 |
25 | - Fix bugs
26 |
27 | ## 0.1.8
28 |
29 | - Fix the wrong text.
30 |
31 | ## 0.1.7
32 |
33 | - Fix bugs
34 |
35 | ## 0.1.6
36 |
37 | - Update status bar item
38 |
39 | - Optimize code
40 |
41 | ## 0.1.5
42 |
43 | - Add Error Message when input is incorrect
44 |
45 | - Optimize user expericence
46 |
47 | ## 0.1.4
48 |
49 | - Fix a bug
50 |
51 | ## 0.1.3
52 |
53 | - Fix a bug
54 |
55 | ## 0.1.2
56 |
57 | - Add
58 | 1. New Icon from icon8
59 | 2. StatusBar at bottom
60 |
61 | - Update README.md
62 |
63 | - Fix
64 | 1. Repeat login on command luogu.login
65 | 2. Display error
66 |
67 | - Update README.md
68 |
69 | ## 0.1.1
70 |
71 | - Update README.md
72 |
73 | - Fix display error
74 |
75 | ## 0.1.0
76 |
77 | - Add
78 | 1. Login in your luogu account
79 | 2. SubmitSolution
80 |
81 | - Optimize user expericence
82 |
83 | - Optimize code
84 |
85 | - Fix bugs
86 |
87 | ## 0.0.5
88 |
89 | - Add error message when user input is incorrect
90 |
91 | - Optimize user expericence
92 |
93 | ## 0.0.4
94 |
95 | - Optimize user expericence
96 |
97 | - Add Problem ID at ViewPage
98 |
99 | ## 0.0.3
100 |
101 | - Optimize user expericence
102 |
103 | ## 0.0.2
104 |
105 | - Fixed bugs
106 | 1. fix luogu.search
107 | 1. highlight.js is not imported
108 | 2. katex is not imported
109 |
110 | ## 0.0.1
111 |
112 | - Add searchProblem Function
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 扩散性百万甜面包
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # vscode-luogu
4 |
5 | 轻松的让你在 VSCode 上提交洛谷代码
6 |
7 | ## 使用说明
8 |
9 | #### **查看题目**
10 |
11 | 1. 按下 `F1` 或 `Ctrl+Shift+P` ,查找`Luogu: 查看题目`命令,点击后输入题号完成搜索
12 |
13 | 2. 点击界面左侧洛谷图片,然后点击搜索图标,输入题号完成搜索
14 |
15 | ## 完成功能
16 |
17 | - 查看题目
18 |
19 |
20 | ## 开发中功能
21 |
22 | - 登录账号
23 |
24 | - 提交代码
25 |
26 | - 题目离线查看
27 |
28 | - 查看自己测评
29 |
30 | - 查看、发布犇犇
31 |
32 | ## Others
33 |
34 | Luogu图标来自[Luogu](https://luogu.org/),严禁商业使用
35 |
36 | UI图标来自:[链接到Icons8](https://icons8.cn/)
37 |
38 | ## LICENSE
39 |
40 | Follow [MIT](LICENSE) LICENSE.
41 |
--------------------------------------------------------------------------------
/STATUS.md:
--------------------------------------------------------------------------------
1 | [](https://www.travis-ci.com/Himself65/vscode-luogu)
2 | [](https://github.com/Himself65/vscode-luogu)
3 | [](https://github.com/Himself65/vscode-luogu)
4 | [](https://marketplace.visualstudio.com/items?itemName=himself6565.vscode-luogu)
5 | [](https://marketplace.visualstudio.com/items?itemName=himself6565.vscode-luogu)
6 | [](https://marketplace.visualstudio.com/items?itemName=himself6565.vscode-luogu)
7 | [](https://github.com/Himself65/vscode-luogu/blob/master/LICENSE)
8 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '^.+\\.tsx?$': 'ts-jest'
4 | },
5 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
6 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-luogu",
3 | "displayName": "vscode-luogu",
4 | "description": "Solve Luogu Problems in VSCode",
5 | "icon": "resources/img/luogu-normal.png",
6 | "version": "0.2.2",
7 | "license": "MIT",
8 | "publisher": "himself6565",
9 | "engines": {
10 | "vscode": "^1.25.0"
11 | },
12 | "homepage": "https://github.com/Himself65/vscode-luogu/blob/master/README.md",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/himself65/vscode-luogu.git"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/himself65/vscode-luogu/issues",
19 | "email": "himself6565@gmail.com"
20 | },
21 | "categories": [
22 | "Other",
23 | "Snippets"
24 | ],
25 | "keywords": [
26 | "luogu",
27 | "algorithm",
28 | "interview",
29 | "OI",
30 | "洛谷"
31 | ],
32 | "preview": true,
33 | "activationEvents": [
34 | "onCommand:luogu.about",
35 | "onCommand:luogu.signin",
36 | "onCommand:luogu.signout",
37 | "onCommand:luogu.userInfo",
38 | "onCommand:luogu.showProblem",
39 | "onCommand:luogu.searchProblem",
40 | "onCommand:luogu.submitSolution",
41 | "onCommand:luogu.refreshExplorer",
42 | "onView:luoguExplorer"
43 | ],
44 | "main": "./dist/extension.js",
45 | "contributes": {
46 | "commands": [
47 | {
48 | "command": "luogu.signin",
49 | "title": "登录洛谷账号",
50 | "category": "Luogu",
51 | "icon": {
52 | "dark": "resources/dark/login-dark.png",
53 | "light": "resources/light/login-light.png"
54 | }
55 | },
56 | {
57 | "command": "luogu.signout",
58 | "title": "退出洛谷账号",
59 | "category": "Luogu"
60 | },
61 | {
62 | "command": "luogu.showProblem",
63 | "title": "显示题目",
64 | "category": "Luogu"
65 | },
66 | {
67 | "command": "luogu.submitSolution",
68 | "title": "提交当前文档代码",
69 | "category": "Luogu"
70 | },
71 | {
72 | "command": "luogu.searchProblem",
73 | "title": "查看题目",
74 | "category": "Luogu",
75 | "icon": {
76 | "dark": "resources/dark/search-dark.png",
77 | "light": "resources/light/search-light.png"
78 | }
79 | },
80 | {
81 | "command": "luogu.refreshExplorer",
82 | "title": "刷新浏览器",
83 | "category": "Luogu",
84 | "icon": {
85 | "dark": "resources/dark/refresh-dark.png",
86 | "light": "resources/light/refresh-light.png"
87 | }
88 | },
89 | {
90 | "command": "luogu.userInfo",
91 | "title": "账户信息",
92 | "category": "Luogu"
93 | },
94 | {
95 | "command": "luogu.about",
96 | "title": "关于",
97 | "category": "Luogu"
98 | },
99 | {
100 | "command": "luogu.selectLanguage",
101 | "title": "选择语言",
102 | "category": "Luogu"
103 | }
104 | ],
105 | "viewsContainers": {
106 | "activitybar": [
107 | {
108 | "id": "luogu",
109 | "title": "Luogu",
110 | "icon": "resources/img/luogu-very-small.png"
111 | }
112 | ]
113 | },
114 | "views": {
115 | "luogu": [
116 | {
117 | "id": "luoguExplorer",
118 | "name": "Problems"
119 | }
120 | ]
121 | },
122 | "menus": {
123 | "view/title": [
124 | {
125 | "command": "luogu.signin",
126 | "when": "view == luoguExplorer",
127 | "group": "navigation@0"
128 | },
129 | {
130 | "command": "luogu.searchProblem",
131 | "when": "view == luoguExplorer",
132 | "group": "navigation@1",
133 | "icon": "resources/search.svg"
134 | },
135 | {
136 | "command": "luogu.refreshExplorer",
137 | "when": "view == luoguExplorer",
138 | "group": "navigation@2"
139 | }
140 | ],
141 | "view/item/context": [
142 | {
143 | "command": "luogu.showProblem",
144 | "when": "view == luoguExplorer && viewItem == problem",
145 | "group": "luogu@1"
146 | }
147 | ],
148 | "explorer/context": [
149 | {
150 | "command": "luogu.submitSolution",
151 | "when": "explorerResourceIsFolder == false",
152 | "group": "luogu@1"
153 | }
154 | ],
155 | "editor/context": [
156 | {
157 | "command": "luogu.submitSolution",
158 | "group": "luogu@1"
159 | }
160 | ]
161 | },
162 | "configuration": [
163 | {
164 | "title": "Luogu",
165 | "properties": {
166 | "luogu.defaultLanguage": {
167 | "type": "string",
168 | "enum": [
169 | "Auto",
170 | "Pascal",
171 | "C",
172 | "C++",
173 | "C++11",
174 | "C++14",
175 | "C++17",
176 | "Python2",
177 | "Python3",
178 | "Pypy2",
179 | "Pypy3",
180 | "Java8",
181 | "Node.js",
182 | "Ruby",
183 | "Go",
184 | "Rust",
185 | "PHP7",
186 | "C#Momo",
187 | "VisualBasic",
188 | "Haskell",
189 | "Kotlin/Native",
190 | "Kotlin/JVM",
191 | "Scala",
192 | "Perl"
193 | ],
194 | "default": "Auto",
195 | "scope": "window",
196 | "description": "Default language for solving the problems."
197 | },
198 | "luogu.showSelectLanguageHint": {
199 | "type": "boolean",
200 | "default": "true",
201 | "scope": "window",
202 | "description": "Show Select Language Hint When Submit"
203 | }
204 | }
205 | }
206 | ]
207 | },
208 | "scripts": {
209 | "vscode:prepublish": "rollup -c",
210 | "compile": "rollup -c",
211 | "watch": "rollup -c -w",
212 | "postinstall": "node ./node_modules/vscode/bin/install",
213 | "test": "cross-env jest -i",
214 | "lint": "tslint -c tslint.json 'src/**/*.ts'",
215 | "lint--fix": "npm run lint --fix"
216 | },
217 | "devDependencies": {
218 | "@types/debug": "^4.1.1",
219 | "@types/fs-extra": "^5.0.4",
220 | "@types/jest": "^24.0.6",
221 | "@types/lodash": "^4.14.115",
222 | "@types/markdown-it": "0.0.4",
223 | "@types/marked": "^0.6.1",
224 | "@types/node": "^7.0.43",
225 | "@types/ws": "^5.1.2",
226 | "concurrently": "^4.0.1",
227 | "cross-env": "^5.2.0",
228 | "jest": "^24.1.0",
229 | "rollup": "^1.2.2",
230 | "rollup-plugin-commonjs": "^9.2.0",
231 | "rollup-plugin-json": "^3.1.0",
232 | "rollup-plugin-node-resolve": "^4.0.0",
233 | "rollup-plugin-terser": "^4.0.4",
234 | "rollup-plugin-typescript": "^1.0.0",
235 | "rollup-plugin-uglify": "^6.0.2",
236 | "ts-jest": "^23.10.5",
237 | "ts-loader": "^5.3.3",
238 | "tslint": "^5.11.0",
239 | "tslint-config-standard": "^8.0.1",
240 | "typescript": "^3.2.2",
241 | "typescript-eslint-parser": "^21.0.2",
242 | "vscode": "^1.1.29"
243 | },
244 | "dependencies": {
245 | "@luogu-dev/markdown-it-katex": "0.0.1",
246 | "axios": "^0.18.0",
247 | "fs-extra": "^7.0.0",
248 | "lodash": "^4.17.13",
249 | "markdown-it": "^8.4.2",
250 | "markdown-it-highlightjs": "^3.0.0",
251 | "marked": "^0.6.1",
252 | "redux": "^4.0.1",
253 | "ws": "^6.0.0"
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/resources/dark/login-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/dark/login-dark.png
--------------------------------------------------------------------------------
/resources/dark/refresh-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/dark/refresh-dark.png
--------------------------------------------------------------------------------
/resources/dark/search-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/dark/search-dark.png
--------------------------------------------------------------------------------
/resources/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/img/favicon.ico
--------------------------------------------------------------------------------
/resources/img/luogu-normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/img/luogu-normal.png
--------------------------------------------------------------------------------
/resources/img/luogu-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/img/luogu-small.png
--------------------------------------------------------------------------------
/resources/img/luogu-very-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/img/luogu-very-small.png
--------------------------------------------------------------------------------
/resources/img/luogu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/img/luogu.png
--------------------------------------------------------------------------------
/resources/light/login-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/light/login-light.png
--------------------------------------------------------------------------------
/resources/light/refresh-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/light/refresh-light.png
--------------------------------------------------------------------------------
/resources/light/search-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/himself65/vscode-luogu/1473c25ef85b7ecd7916a960ab646a3866d002ca/resources/light/search-light.png
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve'
2 | import commonjs from 'rollup-plugin-commonjs'
3 | import json from 'rollup-plugin-json'
4 | import { terser } from 'rollup-plugin-terser'
5 | import typescript from 'rollup-plugin-typescript'
6 |
7 | export default {
8 | input: './src/extension.ts',
9 | output: {
10 | file: 'dist/extension.js',
11 | format: 'cjs'
12 | },
13 | external: ['marked', 'lodash'],
14 | plugins: [
15 | resolve(),
16 | commonjs(),
17 | json(),
18 | typescript(),
19 | terser()
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/commands/SuperCommand.ts:
--------------------------------------------------------------------------------
1 | import debug from '../utils/debug'
2 |
3 | export default class {
4 | public readonly onCommand!: string
5 |
6 | constructor (props: { onCommand: string, handle: Function }) {
7 | Object.assign(this, props)
8 | }
9 |
10 | private handle!: Function
11 |
12 | public callback = () => {
13 | debug(`${this.onCommand} start.`)
14 | this.handle()
15 | debug(`${this.onCommand} end.`)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/commands/about/index.ts:
--------------------------------------------------------------------------------
1 | import SuperCommand from '../SuperCommand'
2 | import debug from '../../utils/debug'
3 | import { promptForOpenOutputChannel } from '../../utils/uiUtils'
4 |
5 | export default new SuperCommand({
6 | onCommand: 'about',
7 | handle: async () => {
8 | debug('About Command clicked.')
9 | // todo: open webview
10 | await promptForOpenOutputChannel('欢迎使用vscode-luogu \n\n 开发者:himself65')
11 | }
12 | })
13 |
--------------------------------------------------------------------------------
/src/commands/index.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 |
3 | import debug from '../utils/debug'
4 | import Search from './search'
5 | import Submit from './submit'
6 | import About from './about'
7 | import { isMaster } from 'cluster'
8 |
9 | const commands = [About, Search, Submit]
10 |
11 | export { commands }
12 |
13 | export function registerCommands (context: vscode.ExtensionContext) {
14 | for (const idx in commands) {
15 | const command = commands[idx]
16 | debug(`register command: ${command.onCommand}.`)
17 | context.subscriptions.push(
18 | vscode.commands.registerCommand(
19 | `luogu.${command.onCommand}`,
20 | () => {
21 | command.callback()
22 | })
23 | )
24 | }
25 | debug('All commands registered.')
26 | }
27 |
28 | export default registerCommands
29 |
--------------------------------------------------------------------------------
/src/commands/login/index.ts:
--------------------------------------------------------------------------------
1 | import SuperCommand from '../SuperCommand'
2 |
3 | export default new SuperCommand({
4 | onCommand: 'signin',
5 | handle: () => {
6 | // todo
7 | }
8 | })
9 |
--------------------------------------------------------------------------------
/src/commands/logout/index.ts:
--------------------------------------------------------------------------------
1 | import SuperCommand from '../SuperCommand'
2 |
3 | export default new SuperCommand({
4 | onCommand: 'signout',
5 | handle: () => {
6 | // todo
7 | }
8 | })
9 |
--------------------------------------------------------------------------------
/src/commands/search/index.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | // @ts-ignore
3 | import marked from 'marked'
4 | import SuperCommand from '../SuperCommand'
5 | import { searchProblem } from '../../utils/api'
6 | import { DialogType, promptForOpenOutputChannel } from '../../utils/uiUtils'
7 | import Problem from '../../model/Problem'
8 |
9 | const generateHTML = (problem: Problem) => `
10 |
11 |
12 |
13 |
14 |
15 | ${problem.name}
16 |
17 |
18 |
19 |
20 | ${marked(problem.toMarkDown())}
21 |
22 | `
23 |
24 | export default new SuperCommand({
25 | onCommand: 'searchProblem',
26 | handle: async () => {
27 | const pid = await vscode.window.showInputBox({
28 | placeHolder: '输入题号'
29 | }).then(res => res ? res.toUpperCase() : null)
30 | if (!pid) {
31 | await promptForOpenOutputChannel('', DialogType.error)
32 | return
33 | }
34 | const problem = await searchProblem(pid).then(res => {
35 | console.log(res)
36 | return res
37 | }).then(res => new Problem(res))
38 | const panel = vscode.window.createWebviewPanel(problem.stringPID, problem.name, vscode.ViewColumn.Two)
39 | panel.webview.html = generateHTML(problem)
40 | }
41 | })
42 |
--------------------------------------------------------------------------------
/src/commands/submit/index.ts:
--------------------------------------------------------------------------------
1 | import SuperCommand from '../SuperCommand'
2 |
3 | export default new SuperCommand({
4 | onCommand: 'submitProblem',
5 | handle: () => {
6 | // todo
7 | }
8 | })
9 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import debug from './utils/debug'
3 |
4 | import RegisterCommands from './commands'
5 | import RegisterViews from './views'
6 |
7 | export async function activate (context: vscode.ExtensionContext): Promise {
8 | debug('initializing luogu-vscode.')
9 |
10 | RegisterCommands(context)
11 | RegisterViews(context)
12 | console.log('init luogu-vscode success.')
13 | }
14 |
15 | export function deactivate (): void {
16 | // Do nothing.
17 | }
18 |
--------------------------------------------------------------------------------
/src/model/Problem.ts:
--------------------------------------------------------------------------------
1 | export interface IAPIProblem {
2 | StringPID: string
3 | Tags: Tag[]
4 | Type: number
5 | Sample: [string[]]
6 | InputFormat: string
7 | OutputFormat: string
8 | Name: string
9 | Hint: string
10 | Flag: string
11 | Description: string
12 | Background: string
13 | Translation?: string
14 | }
15 |
16 | export interface IAPITag {
17 | Id: number
18 | Name: string
19 | ParentId: number
20 | }
21 |
22 | export class Tag {
23 | private id = 0
24 | private name = ''
25 | private parentId = 0
26 |
27 | constructor (fields?: IAPITag) {
28 | if (!fields) {
29 | return
30 | }
31 | this.id = fields.Id
32 | this.name = fields.Name
33 | this.parentId = fields.ParentId
34 | }
35 |
36 | setID (Id: number) {
37 | this.id = Id
38 | }
39 |
40 | getID () {
41 | return this.id
42 | }
43 |
44 | setName (Name: string) {
45 | this.name = Name
46 | }
47 |
48 | getName () {
49 | return this.name
50 | }
51 |
52 | setParentID (ParentId: number) {
53 | this.parentId = ParentId
54 | }
55 |
56 | getParentID () {
57 | return this.parentId
58 | }
59 | }
60 |
61 | export class Problem {
62 | public stringPID = ''
63 | public tags: Tag[] = []
64 | public type = 0
65 | public sample: [string[]] = [[]]
66 | public inputFormat = ''
67 | public outputFormat = ''
68 | public name = ''
69 | public hint = ''
70 | public flag = ''
71 | public description = ''
72 | public background = ''
73 | public translation?: string
74 |
75 | public constructor (
76 | fields?: IAPIProblem
77 | ) {
78 | if (!fields) {
79 | return
80 | }
81 | this.stringPID = fields.StringPID
82 | this.tags = fields.Tags
83 | this.type = fields.Type
84 | this.sample = fields.Sample
85 | this.inputFormat = fields.InputFormat
86 | this.outputFormat = fields.OutputFormat
87 | this.name = fields.Name
88 | this.hint = fields.Hint
89 | this.flag = fields.Flag
90 | this.description = fields.Description
91 | this.background = fields.Background
92 | this.translation = fields.Translation
93 | }
94 |
95 | toHTML (): string {
96 | let sample = ''
97 | this.sample.forEach((array, index) => {
98 | sample += `输入${index + 1}:
99 |
100 | ${array[0]}
101 |
102 | 输出${index + 1}:
103 |
104 | ${array[1]}
105 |
106 | `
107 | })
108 | return `
109 |
110 |
111 |
112 |
113 |
114 | ${this.name}
115 |
116 |
117 | ${this.name}
118 | 题目描述
119 | ${this.translation || ''}
120 | ${this.background}
121 | ${this.description}
122 | 输入输出格式
123 | 输入格式
124 | ${this.inputFormat}
125 | 输出格式
126 | ${this.outputFormat}
127 | 输入输出样例
128 | ${sample}
129 | 说明
130 | ${this.hint}
131 |
132 | `
133 | }
134 |
135 | toMarkDown (): string {
136 | console.log(this.translation)
137 |
138 | let sample = ''
139 | this.sample.forEach((array, index) => {
140 | sample += `输入${index + 1} : \n \`\`\` \n ${array[0]} \n \`\`\` \n 输出${index + 1} : \n \`\`\` \n ${array[1]} \n \`\`\` \n`
141 | })
142 | return ` # ${this.name}| [${this.stringPID}](https://www.luogu.org/problemnew/show/${this.stringPID}) \n \n ${this.translation || ''} \n \n ## 题目描述 \n \n ${this.background} \n \n ${this.description} \n \n ## 输入输出格式 \n \n **输入格式** \n \n ${this.inputFormat} \n \n **输出格式** \n \n ${this.outputFormat} \n \n ## 输入输出样例 \n \n ${sample} \n \n ## 说明 \n \n ${this.hint} \n`
143 | }
144 | }
145 |
146 | export default Problem
147 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux'
2 |
3 | function appStore () {
4 |
5 | }
6 |
7 | export * from './shared'
8 |
9 | export const store = createStore(appStore)
10 |
11 | export default store
12 |
--------------------------------------------------------------------------------
/src/store/shared.ts:
--------------------------------------------------------------------------------
1 | export const USER_UPDATE = Symbol('user-update')
2 |
--------------------------------------------------------------------------------
/src/store/userManager.ts:
--------------------------------------------------------------------------------
1 | export const type = {
2 | USER_LOGOUT: Symbol('user-logout'),
3 | USER_LOGIN: Symbol('user-login')
4 | }
5 |
6 | export function loginUser () {
7 | return {
8 | type: type.USER_LOGIN
9 | }
10 | }
11 |
12 | export function logoutUser () {
13 | return {
14 | type: type.USER_LOGOUT
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/utils/api.ts:
--------------------------------------------------------------------------------
1 | import _ from 'axios'
2 |
3 | export namespace API {
4 | export const baseURL = 'https://www.luogu.org'
5 | export const apiURL = '/api'
6 | export const SEARCH_PROBLEM = (pid: string) => API.apiURL + '/problem/detail' + `/${pid}`
7 | export const ACCESS_TOKEN = '/OAuth2/accessToken'
8 | }
9 |
10 | export const axios = _.create({
11 | baseURL: API.baseURL
12 | })
13 |
14 | export const searchProblem = async (pid: string) =>
15 | axios.get(API.SEARCH_PROBLEM(pid))
16 | .then(res => res.data.data || null)
17 |
18 | export const OAUTH2_INFO = {
19 | grant_type: 'password',
20 | client_id: 'luogu-vscode',
21 | client_secret: 'Asdf1234Excited111'
22 | }
23 |
24 | export const login = async (username: string, password: string) =>
25 | axios.post(API.ACCESS_TOKEN, {
26 | OAUTH2_INFO,
27 | username,
28 | password
29 | }).then(res => res.data || null)
30 |
31 | export default axios
32 |
--------------------------------------------------------------------------------
/src/utils/debug.ts:
--------------------------------------------------------------------------------
1 | export const isDev = process.env.NODE_ENV === 'development'
2 |
3 | let time = 0
4 |
5 | export function debug (message?: any, ...optionalParams: any[]) {
6 | if (isDev) {
7 | if (time !== 0) {
8 | time = Date.now() - time
9 | }
10 | console.log(`${message} ${time ? `// ${time}ms` : ''}`, ...optionalParams)
11 | time = Date.now()
12 | }
13 | }
14 |
15 | export default debug
16 |
--------------------------------------------------------------------------------
/src/utils/shared.ts:
--------------------------------------------------------------------------------
1 | export enum UserStatus {
2 | Login = 1,
3 | Logout = 2
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/uiUtils.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { luoguChannel } from '../views/luoguChannel'
3 |
4 | export namespace DialogOptions {
5 | export const open: vscode.MessageItem = { title: 'Open' }
6 | export const yes: vscode.MessageItem = { title: 'Yes' }
7 | export const no: vscode.MessageItem = { title: 'No', isCloseAffordance: true }
8 | export const never: vscode.MessageItem = { title: 'Never' }
9 | export const singUp: vscode.MessageItem = { title: 'Sign up' }
10 | }
11 |
12 | export async function promptForOpenOutputChannel (message: string, type: DialogType = DialogType.info, channel: vscode.OutputChannel = luoguChannel): Promise {
13 | let result: vscode.MessageItem | undefined
14 | switch (type) {
15 | case DialogType.info:
16 | result = await vscode.window.showInformationMessage(message, DialogOptions.open, DialogOptions.no)
17 | break
18 | case DialogType.warning:
19 | result = await vscode.window.showWarningMessage(message, DialogOptions.open, DialogOptions.no)
20 | break
21 | case DialogType.error:
22 | result = await vscode.window.showErrorMessage(message, DialogOptions.open, DialogOptions.no)
23 | break
24 | default:
25 | break
26 | }
27 |
28 | if (result === DialogOptions.open) {
29 | channel.show()
30 | }
31 | }
32 |
33 | export async function showFileSelectDialog (): Promise {
34 | const defaultUri: vscode.Uri | undefined = vscode.workspace.rootPath ? vscode.Uri.file(vscode.workspace.rootPath) : undefined
35 | const options: vscode.OpenDialogOptions = {
36 | defaultUri,
37 | canSelectFiles: true,
38 | canSelectFolders: false,
39 | canSelectMany: false,
40 | openLabel: 'Select'
41 | }
42 | return vscode.window.showOpenDialog(options)
43 | }
44 |
45 | export enum DialogType {
46 | info = 'info',
47 | warning = 'warning',
48 | error = 'error'
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/userManager.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs'
2 | import { resolve } from 'path'
3 | import { EventEmitter } from 'events'
4 | import { has } from 'lodash'
5 | import debug from './debug'
6 | import luoguStatusBar from '../views/luoguStatusBar'
7 | import { UserStatus } from './shared'
8 |
9 | export const SETTINGS_PATH = resolve('.luogu', 'settings.json')
10 |
11 | interface ISetting {
12 | username?: string
13 | access_token?: string
14 | refresh_token?: string
15 | }
16 |
17 | // todo
18 | class UserManager extends EventEmitter {
19 | private settings: ISetting = {}
20 |
21 | constructor () {
22 | super()
23 | this.on('login', () => {
24 | luoguStatusBar.updateStatusBar(UserStatus.Login)
25 | })
26 | this.on('logout', () => {
27 | luoguStatusBar.updateStatusBar(UserStatus.Logout)
28 | })
29 | try {
30 | this.settings = JSON.parse(readFileSync(SETTINGS_PATH, { encoding: 'utf-8' }))
31 | } catch (e) {
32 | console.error(e)
33 | }
34 | if (!has(this.settings, 'access_token')) {
35 | debug('Can\'t find access_token in settings.')
36 | this.emit('logout')
37 | } else {
38 | // check token expires
39 | const refreshToken = this.settings.refresh_token
40 | }
41 | }
42 | }
43 |
44 | export default new UserManager()
45 |
--------------------------------------------------------------------------------
/src/views/index.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import luoguStatusBar from './luoguStatusBar'
3 | import luoguChannel from './luoguChannel'
4 |
5 | export function registerViews (context: vscode.ExtensionContext) {
6 | context.subscriptions.push(
7 | luoguStatusBar,
8 | luoguChannel
9 | )
10 | }
11 |
12 | export default registerViews
13 |
--------------------------------------------------------------------------------
/src/views/luoguChannel.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 |
3 | class LuoguChannel implements vscode.Disposable {
4 | readonly name: string = 'luogu'
5 | private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel('luogu')
6 |
7 | public appendLine (message: string): void {
8 | this.channel.appendLine(message)
9 | }
10 |
11 | public append (message: string): void {
12 | this.channel.append(message)
13 | }
14 |
15 | public show (): void {
16 | this.channel.show()
17 | }
18 |
19 | public clear (): void {
20 | this.channel.clear()
21 | }
22 |
23 | public hide (): void {
24 | this.channel.hide()
25 | }
26 |
27 | public dispose (): void {
28 | this.channel.dispose()
29 | }
30 | }
31 |
32 | export const luoguChannel = new LuoguChannel()
33 |
34 | export default luoguChannel
35 |
--------------------------------------------------------------------------------
/src/views/luoguStatusBar.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { UserStatus } from '../utils/shared'
3 | import debug from '../utils/debug'
4 |
5 | export class LuoguStatusBar implements vscode.Disposable {
6 | private readonly statusBarItem: vscode.StatusBarItem
7 |
8 | constructor () {
9 | debug('initializing luoguStatusBarItem.')
10 | this.statusBarItem = vscode.window.createStatusBarItem()
11 | this.statusBarItem.command = 'luogu.userInfo'
12 | debug('init luoguStatusBarItem finished.')
13 | }
14 |
15 | public updateStatusBar (status: UserStatus): void {
16 | // todo: update text content
17 | switch (status) {
18 | case UserStatus.Login:
19 | this.statusBarItem.text = '洛谷已登录'
20 | this.statusBarItem.show()
21 | break
22 | case UserStatus.Logout:
23 | default:
24 | this.statusBarItem.text = '暂未登陆洛谷账户'
25 | this.statusBarItem.show()
26 | break
27 | }
28 | debug(`luoguStatusBarItem: userStatus ${UserStatus[status]}.`)
29 | }
30 |
31 | public dispose (): void {
32 | debug('luoguStatusBarItem: dispose.')
33 | // todo
34 | }
35 | }
36 |
37 | export const luoguStatusBar = new LuoguStatusBar()
38 |
39 | export default luoguStatusBar
40 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | yarn
4 | yarn run test
5 |
--------------------------------------------------------------------------------
/tests/index.test.ts:
--------------------------------------------------------------------------------
1 | describe('base test', () => {
2 | it('should pass test', () => {
3 | expect(1).toBe(1)
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "dist",
6 | "lib": [
7 | "es6",
8 | "es2015",
9 | "es2016",
10 | "es2017",
11 | "es2018"
12 | ],
13 | "sourceMap": true,
14 | "rootDir": "src",
15 | "strict": true
16 | },
17 | "exclude": [
18 | "node_modules/**/*",
19 | ".vscode-test",
20 | "CHANGELOG.md",
21 | ".travis.yml",
22 | ".gitignore",
23 | ".gitattributes",
24 | "CHANGELOG.md",
25 | "tests"
26 | ],
27 | "include": [
28 | "./src/**/*"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint-config-standard",
3 | "linterOptions": {
4 | "include": [
5 | "src/**/*.ts"
6 | ],
7 | "exclude": [
8 | "**/*.test.(j|t)s",
9 | "node_modules/**/*",
10 | "dist"
11 | ]
12 | },
13 | "rules": {
14 | "no-string-throw": true,
15 | "no-unused-expression": true,
16 | "no-duplicate-variable": true,
17 | "curly": true,
18 | "class-name": true,
19 | "semicolon": [
20 | false,
21 | "always"
22 | ],
23 | "use-type-alias": true,
24 | "no-inferrable-types": true,
25 | "unified-signatures": true,
26 | "use-default-type-parameter": true,
27 | "no-undefined-argument": true,
28 | "no-unused-variable": false,
29 | "no-invalid-this": false,
30 | "no-empty": {
31 | "severity": "warn"
32 | },
33 | "restrict-plus-operands": false,
34 | "no-useless-cast": false,
35 | "variable-name": {
36 | "severity": "warn"
37 | },
38 | "allowUnusedLabels": false
39 | },
40 | "rulesDirectory": []
41 | }
42 |
--------------------------------------------------------------------------------