├── .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 | [![Build Status](https://www.travis-ci.com/Himself65/vscode-luogu.svg?branch=master)](https://www.travis-ci.com/Himself65/vscode-luogu) 2 | [![Top Language](https://img.shields.io/github/languages/top/himself65/vscode-luogu.svg)](https://github.com/Himself65/vscode-luogu) 3 | [![Code Size](https://img.shields.io/github/languages/code-size/himself65/vscode-luogu.svg)](https://github.com/Himself65/vscode-luogu) 4 | [![VERSION](https://vsmarketplacebadge.apphb.com/version/himself6565.vscode-luogu.svg)](https://marketplace.visualstudio.com/items?itemName=himself6565.vscode-luogu) 5 | [![DownloadCount](https://vsmarketplacebadge.apphb.com/installs/himself6565.vscode-luogu.svg)](https://marketplace.visualstudio.com/items?itemName=himself6565.vscode-luogu) 6 | [![RATING](https://vsmarketplacebadge.apphb.com/rating-star/himself6565.vscode-luogu.svg)](https://marketplace.visualstudio.com/items?itemName=himself6565.vscode-luogu) 7 | [![LICENSE](https://img.shields.io/badge/license-MIT-green.svg)](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 | --------------------------------------------------------------------------------