新闻详情

新闻详情

首页 / 资讯中心 / 详情

VS Code 插件开发实战:拼题A刷题、本地编译与自动提交

发布时间:2026/9/18 10:46:40来源:尧图网络
VS Code 插件开发实战:拼题A刷题、本地编译与自动提交
1. 为什么我要在编辑器里写拼题A的题先说清楚这个插件是干什么的。拼题APintia是很多高校程序设计与数据结构课程在用的在线判题平台题目质量不错但它的使用体验有一个绕不开的痛点你必须在浏览器和编辑器之间来回切。看题在网页写代码在本地交题又要复制粘贴回网页中间还夹着编译器报错、样例对不上、格式错一行就WA的情况。我上学期带实验课的时候一个班四十多人在机房同时刷题光把代码从本地粘到判题页面这个动作就浪费了大量时间更别说题目里的样例输入得手动敲进去测试。所以我花了大概两周的业余时间写了这么个 VS Code 插件名字就叫 Pintia Helper。核心目标很明确把读题—写码—运行样例—提交—看结果这一整条链路收进 VS Code 里让写题这件事像写普通项目一样自然。你不需要记网页的快捷键不需要复制粘贴甚至不需要离开编辑器就能看到判题结果和错误提示。这篇文章写给两类人。一类是想直接装来用的同学我会把安装、配置、快捷键讲透另一类是想自己动手写一个类似插件的开发者我会把核心架构、接口调用、调试踩坑全部分享出来。整个过程涉及 VS Code 插件开发、TypeScript、Webview 通信、本地进程调用这些技术点但我会尽量用大白话解释基础薄弱的也能看下去。需要提前说明的一点插件的实现基于拼题A公开的网页接口和页面结构所有请求都走你本人的登录态不涉及任何绕过平台机制的行为。如果你打算长期使用建议关注官方是否有开放API的计划这样插件的稳定性会更好。2. 插件整体设计与技术选型拆解2.1 为什么选 VS Code 插件而不是别的形态一开始我考虑过三个方向做一个独立桌面应用、做一个浏览器油猴脚本、做 VS Code 插件。独立桌面应用的问题在于你写代码的环境本来就是编辑器再开一个应用等于又多了一个窗口还是没有解决切来切去的核心问题。而且跨平台打包 Electron 那一套体积动辄上百兆为了刷题装个这玩意儿不划算。油猴脚本的方案我实际试过一版。好处是能直接操作拼题A的页面DOM抓取题目信息很方便但坏处也很明显读题还是在浏览器里代码还是在编辑器里写本质上没跳出原来的框。而且浏览器沙箱对本地文件读写、子进程调用限制太多想直接编译运行你的C代码基本不可能。VS Code 插件就刚好卡在中间。它本身就跑在编辑器里能直接操作工作区文件能调用终端能通过child_process启动编译器还能用 Webview 渲染题面。最关键是VS Code 的扩展生态成熟调试插件有内置的 Extension Host 调试器开发体验很顺。这也是我最终选它的决定性理由。提示如果你从来没有开发过 VS Code 插件别被插件开发这个词吓到。它的本质就是一个 Node.js 程序用一个package.json声明能力再用activate函数注册命令入门门槛比想象中低。2.2 插件的三段式架构整个插件我拆成三层各层职责分得很清楚后面维护起来才不至于一团乱麻。第一层是命令与生命周期层也就是extension.ts里注册的那些命令比如登录拼题A打开题目列表提交当前文件。这一层只做参数校验和入口分发不写业务逻辑。第二层是服务层包括 HTTP 请求封装、题目数据解析、会话状态管理、编译器调用。这层是纯 TypeScript 逻辑不依赖 VS Code API好处是可以单独写单元测试不用启动整个编辑器实例。第三层是视图层也就是 Webview 面板和 TreeView 侧边栏。题面用 Webview 渲染因为要保留 HTML 格式和公式题目列表用 TreeView因为它能天然支持树状分组和图标。这种分层的直接收益是后来拼题A改了一次题面DOM结构我只改了服务层的解析函数视图层和命令层一行没动二十分钟就修好了。如果当初图省事把所有逻辑塞在一个文件里那次改动至少要花半天。2.3 用本地文件系统做数据缓存题目信息、登录Cookie、提交记录这些数据我全部存在 VS Code 的globalStorageUri目录下而不是塞进globalState。原因是globalState适合存小型的键值对题面HTML动辄几十KB提交记录会越积越多全塞进去会让状态文件变得臃肿还可能触发大小限制。缓存策略上我做了两级内存里用 Map 存最近打开的题目避免短时间内反复请求磁盘上按题目ID存 JSON带时间戳。默认缓存有效期设成24小时超过就重新拉取这样既能减少请求次数又不至于题目更新了你还看到旧题面。interface CachedProblem { id: string; title: string; html: string; samples: Array{ input: string; output: string }; fetchedAt: number; } const CACHE_TTL 24 * 60 * 60 * 1000; // 24小时这个CACHE_TTL后来被我加进了配置项因为有些同学刷的是老师临时改过的题缓存太久反而误事。2.4 编译器路径为什么不写死最初版本我图快直接把g写死在配置里结果在 Windows 上翻车了。很多同学的 MinGW-w64 装在C:\mingw64\bin或者C:\Program Files\mingw-w64\...路径五花八门硬编码必然失败。后来改成从配置读取默认值给g走 PATH同时提供一个自动探测按钮扫描常见安装位置。实测下来这个设计救了不少人——尤其是用 Scoop 或 MSYS2 装环境的同学他们的编译器路径和典型安装完全不一样。{ pintia.compiler.cpp: g, pintia.compiler.c: gcc, pintia.compiler.python: python, pintia.compiler.java: javac, pintia.timeoutMs: 5000 }timeoutMs这个参数也不是拍脑袋定的。在线判题一般给单题1到2秒本地跑样例用5秒足够覆盖绝大多数情况同时能防止死循环把你的机器拖垮。3. 核心功能的实现细节与关键代码3.1 登录态维持Cookie 到底怎么存拼题A的登录流程是标准的表单提交成功后服务端下发会话Cookie。插件要能免登录访问题目和提交就必须把这个Cookie持久化下来。我的做法是在globalStorageUri下建一个session.json只存必要的Cookie字段并做基础的混淆处理。这里要说清楚任何本地存储都不是绝对安全的所以我在文档里明确写了请不要在公用电脑上保存登录态并且加了一个退出登录命令一键清空。async function saveSession(cookies: string) { const storagePath context.globalStorageUri.fsPath; await fs.ensureDir(storagePath); const file path.join(storagePath, session.json); await fs.writeFile(file, JSON.stringify({ cookies, savedAt: Date.now() }), utf8); }请求时把Cookie塞进headers的Cookie字段。这里有个坑axios默认不自动带上Cookie必须手动设置而且要注意withCredentials在 Node 环境下不生效得靠手动拼headers。3.2 题面解析DOM 结构变了怎么办题面解析是最容易碎的环节。拼题A的题目页面用的是服务端渲染的HTML我原本用 CSS 选择器精确取值比如.problem-content .description。结果平台前端改版一次选择器全失效。后来我改成三层容错策略第一层尝试精确选择器第二层退化到按标题文本定位找包含题目描述字样的节点取它的同级内容第三层如果还是拿不到就把整段正文HTML原样丢给Webview让用户自己看。function extractProblemContent(doc: Document): string { // 第一层精确选择器 let node doc.querySelector(.problem-description .content); if (node) return node.innerHTML; // 第二层按标题文本定位 const headings Array.from(doc.querySelectorAll(h1,h2,h3,h4)); const target headings.find(h /题目描述|问题描述/.test(h.textContent || )); if (target?.nextElementSibling) { return target.nextElementSibling.innerHTML; } // 第三层兜底 return doc.body.innerHTML; }这个思路值得所有做网页抓取的同行参考不要把解析逻辑写成全有或全无多留几条退路线上稳定性会好很多。3.3 样例提取与自动生成测试文件样例对不上是刷题最头疼的事之一。我做了自动提取把题面里的样例输入/样例输出抓出来然后在工作区生成对应的.in和.out文件。命名规则是{题目ID}_sample_{序号}.in。为什么要带题目ID因为一个工作区里可能同时开着好几道题的文件不带前缀会互相覆盖。序号则是为了支持多组样例的情况。function writeSamples(dir: string, problemId: string, samples: Sample[]) { samples.forEach((s, idx) { fs.writeFileSync(path.join(dir, ${problemId}_sample_${idx}.in), s.input); fs.writeFileSync(path.join(dir, ${problemId}_sample_${idx}.out), s.output); }); }提取的时候要注意一个问题网页里的样例通常包在pre标签里得到的文本会带前后空白和制表符。提交给判题系统时行尾多余空格可能导致WA所以我在写入文件前做了一次规范化统一换行符为\n去掉每行尾部空白但保留行首缩进因为有些题目对格式敏感。3.4 本地运行与结果比对运行逻辑是把当前文件编译成可执行文件用样例.in作为标准输入跑一遍把实际输出和.out逐行比对。编译产物放在工作区的.pintia/build目录加进.gitignore模板里。编译命令根据不同语言分派C 用g -O2 -stdc17Python 直接pythonJava 先javac再java。比对逻辑我没有用简单的字符串全等而是做了尾部空白容忍和行尾换行容忍因为不同IDE保存文件时的换行处理不一样。function compareOutput(actual: string, expected: string, strict: boolean): boolean { const norm (s: string) strict ? s.replace(/\r\n/g, \n) : s.replace(/\r\n/g, \n).split(\n).map(l l.trimEnd()).join(\n).trimEnd(); return norm(actual) norm(expected); }strict模式默认关闭提供配置开关。我实测发现绝大多数题目在非严格模式下都能正确判断只有极少数格式题需要打开严格模式。注意本地跑通不代表判题通过。本地样例往往只是题面给出的少数几组隐藏测试点可能覆盖边界情况。跑通样例只是及格线不是满分保证。3.5 提交与结果回显提交是把代码POST到拼题A的提交接口然后轮询结果。这里有两个技术点值得说。第一是提交频率限制。判题平台普遍有提交间隔限制短时间高频提交会触发限制。所以我在提交命令里加了节流同一题目30秒内只允许提交一次界面上给倒计时提示。第二是结果轮询。提交后返回的是一个提交ID需要按这个ID去查结果。我用了指数退避的轮询策略先隔1秒查一次没结果就2秒、4秒最多查到15秒。这样既不会空耗请求又能及时拿到结果。async function pollResult(submissionId: string) { const delays [1000, 2000, 4000, 8000]; for (const d of delays) { await sleep(d); const res await fetchResult(submissionId); if (res.status ! PENDING res.status ! JUDGING) return res; } throw new Error(判题超时请手动到网页查看结果); }结果回显用 Webview 面板把每个测试点的状态、耗时、内存列成表格。这个表格的视觉效果我调了好几版最后定成绿色通过、红色失败、黄色超时一眼就能看出问题出在哪个测试点。4. 从零搭建这个插件的完整实操流程4.1 开发环境准备与项目初始化先决条件Node.js 18以上、VS Code 1.80以上、一个全局安装的yo和generator-code。npm install -g yo generator-code yo code生成向导里选 New Extension (TypeScript)填好插件名和标识符它会生成标准目录结构。关键文件是package.json声明贡献点和src/extension.ts入口。npm install npm run compile编译通过后按 F5会弹出一个新的扩展开发宿主窗口里面就能调试你的插件了。这里我第一次踩坑F5 启动的窗口里你的插件是加载的但工作区是你当前打开的目录如果你想测试题目文件生成功能得先在宿主窗口里打开一个真实的文件夹。4.2 声明贡献点与命令注册package.json里的contributes是插件的门面所有命令、配置、菜单都要在这里声明否则用户根本找不到入口。{ contributes: { commands: [ { command: pintia.login, title: 拼题A: 登录 }, { command: pintia.openProblem, title: 拼题A: 打开题目 }, { command: pintia.runSample, title: 拼题A: 运行样例 }, { command: pintia.submit, title: 拼题A: 提交当前文件 } ], keybindings: [ { command: pintia.runSample, key: ctrlaltr, when: editorTextFocus } ] } }命令注册在activate函数里用vscode.commands.registerCommand把命令ID和实际处理函数绑定。这里有个容易忽略的点注册命令时一定要push到context.subscriptions否则插件停用时会残留调试时反复激活可能导致命令重复注册。4.3 本地编译运行的进程调用实现调用编译器的核心是child_process.execFile它比exec更安全因为不会经过shell解析参数里的特殊字符不会被误解释。import { execFile } from child_process; function runProgram(exePath: string, inputPath: string, timeout: number) { return new Promisestring((resolve, reject) { const child execFile(exePath, [], { timeout, maxBuffer: 10 * 1024 * 1024, cwd: path.dirname(exePath) }, (err, stdout, stderr) { if (err) reject(stderr || err.message); else resolve(stdout); }); const input fs.readFileSync(inputPath, utf8); child.stdin?.end(input); }); }几个参数我都是斟酌过的maxBuffer设10MB因为有些题的输出量很大默认的1MB会直接截断timeout用配置项默认5秒输入直接stdin.end(input)写入比 spawn 再手动写流简单。Windows 上还有个特殊处理可执行文件要加.exe后缀且路径里的空格要正确处理。execFile传数组参数能避开空格问题比手动拼字符串靠谱。4.4 Webview 题面渲染与消息通信Webview 是个隔离环境和扩展主进程之间靠postMessage通信。题面渲染时我会注入一段CSS保证代码块、公式、表格在深色主题下也能看清。const panel vscode.window.createWebviewPanel( pintiaProblem, 题目 ${problem.id}, vscode.ViewColumn.Beside, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [context.extensionUri] } ); panel.webview.html renderProblemHtml(problem);retainContextWhenHidden这个选项建议打开否则切走再切回来时你在题面里做的滚动、折叠状态全丢了体验很差。代价是内存占用高一些但对刷题场景完全可以接受。通信方向有两个Webview 往扩展发用户点了提交按钮扩展往 Webview 发判题结果来了请刷新表格。都要用panel.webview.onDidReceiveMessage和panel.webview.postMessage配对处理。4.5 打包发布 vsix开发完成后用vsce打包npm install -g vscode/vsce vsce package会生成一个.vsix文件。安装方式有两种命令行code --install-extension pintia-helper-0.1.0.vsix或者在 VS Code 扩展面板右上角菜单里选从 VSIX 安装。打包时经常报两个错一是README.md不存在二是repository字段缺失。前者写一个说明文件即可后者在package.json里补上即可。还有engines.vscode版本别填太高否则低版本 VS Code 的用户装不上我设的是^1.80.0覆盖了绝大多数还在维护的版本。5. 开发与使用中的踩坑记录5.1 常见问题速查表现象可能原因解决办法登录后仍然提示未登录Cookie 未正确写入请求头检查session.json是否存在重新登录题面显示为空白DOM 选择器失效检查插件版本更新解析规则编译报找不到 g编译器不在 PATH在设置里填绝对路径运行样例卡住不动程序死循环检查 timeout 配置确认输入文件正确提交提示频率限制短时间重复提交等待30秒后重试结果一直 PENDING平台判题队列繁忙稍等手动刷新或到网页查看输出比对总是失败行尾空白差异关闭严格模式检查换行符Webview 题面样式错乱深色主题冲突更新插件已内置主题适配这张表是我自己遇到过的和收集用户反馈整理出来的实际覆盖了九成以上的问题。建议先对着表自查绝大多数情况不用找人问。5.2 几个让我印象深刻的坑第一个坑Extension Host 的内存泄漏。我早期版本每次打开题目都新建一个 WebviewPanel但没在关闭时清理事件监听器。结果连续打开二十几道题后宿主进程内存飙到1G以上编辑器开始卡顿。解决方法是把panel.onDidDispose里的监听器全部释放并且复用同一个面板而不是反复创建。第二个坑异步任务没被取消。用户点了运行样例程序在跑这时他又点了一次两个进程同时跑输出混在一起。修复的方式是给每个运行任务分配一个唯一ID新任务启动前先杀掉旧的子进程。这提醒我任何用户可重复触发的操作都要考虑重入问题。第三个坑跨平台的路径分隔符。我本地是 Mac写死了/结果 Windows 用户反馈生成的样例文件路径不对。后来统一改用path.join这个问题再没出现过。跨平台开发中永远不要手动拼路径字符串这是我付出代价换来的经验。第四个坑配置文件热更新。用户改了编译器路径插件却还在用旧值。原因是配置是在激活时读一次就缓存了。后来我监听onDidChangeConfiguration配置一变就重新读取体验立刻顺了。5.3 一些提升体验的小细节代码片段Snippet功能值得单独说。我在插件里内置了一套常用模板比如C的快速输入输出、并查集、二分查找通过CompletionItemProvider注入。刷题时敲pintia-main就能展开完整的主函数框架敲pintia-dsu展开并查集模板。这个功能用户反馈极好实际上是投入产出比最高的一块。另一个细节是状态栏。我在状态栏放了一个小图标显示当前登录状态和当前题目的ID点击能快速打开题目面板。不占地方但能让用户随时知道自己在哪道题上。还有就是错误提示的措辞。早期编译失败我直接抛原始stderr用户看一堆英文报错完全懵。后来改成如果是常见的语法错误给一句中文提示加原始报错如果是找不到编译器直接告诉用户去哪个设置项改。报错信息是产品体验的一部分这句话我深有体会。6. 后续可以怎么扩展这个插件目前只覆盖了刷题主流程但可扩展的方向不少。一是代码模板库的社区化。现在模板是我一个人写的如果做成可配置的远程模板仓库用户可以自己贡献覆盖面会广很多。二是题解笔记联动。做完一道题把思路记录和代码存在同一个目录插件自动生成一个 Markdown 笔记骨架。这个功能对复习特别有用我自己现在就是这么做笔记的。三是多平台适配。如果后续有类似平台开放接口可以在服务层做抽象一套界面适配多个后端架构上我当初分层就是为这个留的口子。四是离线题库。把做过的题目和他的提交记录存成本地数据库支持全文搜索。这个需求在学生复习阶段特别强烈。我个人在实际维护中的体会是插件类项目的核心竞争力不在功能有多少而在主流程是否足够顺滑。用户能忍功能少忍不了每天都要为同一个卡点浪费时间。所以与其铺功能不如把打开题目到看到结果这条路径打磨到三秒内完成这才是真正留住人的地方。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

IDEA 常用设置指南:编码、构建、内存与插件优化 2026/9/18 11:34:56

IDEA 常用设置指南:编码、构建、内存与插件优化

在 IDEA 里敲代码这件事,很多人是从"装完就用默认设置"开始的,然后一用就是好几年。默认配置不是不能用,它只是"平均意义上的能用"——照顾了所有人的最低共识,却没有照顾你的手感、你的项目结构、你的机器配…

阅读更多 →
Pandoc教程:Markdown、Word、PDF、HTML一键互转 2026/9/18 11:34:56

Pandoc教程:Markdown、Word、PDF、HTML一键互转

先说结论:如果你经常需要在 Markdown、Word、HTML、PDF 甚至 epub 之间来回转换文档,Pandoc 就是那个你迟早会装上、装上之后就再也离不开的工具。这货本质上是一个文档格式转换器,但它的能力早就超出了“转换”这两个字。你可以用 Markdown …

阅读更多 →
Fiddler抓包全攻略:电脑端、手机真机与雷电模拟器HTTPS解密实战 2026/9/18 11:34:56

Fiddler抓包全攻略:电脑端、手机真机与雷电模拟器HTTPS解密实战

问你个问题:你的手机App,每次打开都和后端服务器说了哪些“悄悄话”?换成工程师的说法,就是它发了什么请求、带了什么参数、拿到了什么返回。https抓包工具就是干这个的,把流量从设备里“请”出来,放到电脑…

阅读更多 →
CUDA异步传输实战:用流与事件消除数据传输瓶颈 2026/9/18 11:34:56

CUDA异步传输实战:用流与事件消除数据传输瓶颈

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
QMK Iron165 键盘移植实践:基于 STM32F072 的 Smith+Rune Iron165 矩阵、背光与键位配置 2026/9/18 11:34:56

QMK Iron165 键盘移植实践:基于 STM32F072 的 Smith+Rune Iron165 矩阵、背光与键位配置

QMK Iron165 键盘移植实践:基于 STM32F072 的 SmithRune Iron165 矩阵、背光与键位配置 【免费下载链接】qmk_firmware Open-source keyboard firmware for Atmel AVR and Arm USB families 项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware …

阅读更多 →
SpringBoot零侵入性能监控系统设计与实现 2026/9/18 11:31:53

SpringBoot零侵入性能监控系统设计与实现

1. 项目概述在当今企业级应用开发中,SpringBoot已经成为Java生态中最受欢迎的框架之一。然而随着业务规模扩大,性能问题往往成为最棘手的挑战。传统的性能监控方案通常需要修改应用代码或配置,这在生产环境中往往不可行。我们团队开发的这套系…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞