C Bulk Formatter(C 批量格式化插件)面向 C / 嵌入式工程的格式化插件,主打批量与安全:
设计原则:宁可漏做,绝不误改。 一、处理流程(严格按顺序)
编码判定规则(最保守)只有在「100% 确认」时才转码,否则一律不动。判定顺序:
字节安全通道还有一道硬校验:处理前后「去掉空白后的字节序列」必须逐字节相同( 编码策略
|
| 值 | 行为 |
|---|---|
byte-safe(默认) |
只对"确认是文本、仅编码存疑"(utf8-damaged)的文件启用:不转码,只改空白,并列入「需人工确认」 |
skip |
只要编码无法确认就整个跳过,不写盘 |
force-gbk |
明确承担风险:强制按 GBK 解码后转 UTF-8 |
force-utf8 |
明确承担风险:强制按 UTF-8 解码(非法字节变 U+FFFD)后写回 |
注意:GBK 解码依赖 Node 的 full-icu。VSCode 内置 Node 已包含,正常可用;若探测不到解码器,会明确报错而不是静默乱码。
二、缩进能力
{}块缩进switch / case / default分支- 括号内折行的续行缩进
/* ... */块注释(含*对齐风格)、//行注释#预处理指令置顶、宏\续行缩进- 字符串 / 字符常量 / 注释中的
{}()不参与计数 - 注释掉的代码也可格式化(
formatCodeInComments,默认关闭,见下)
注释掉的代码(/* ... */)也一起格式化
很多老工程会把整段函数用 /* */ 注掉留着,这些代码现在的缩进是"压平"的。
打开 cBulkFormatter.formatCodeInComments 后,插件会:
- 识别:向后续扫描到注释结束,若注释内容去空行后 ≥3 行且 60% 以上像代码
(以
{/}/;结尾、以 C 关键字开头、赋值、函数调用、i++等)→ 判定为"被注释掉的代码"; 散文注释(说明文字)不会被误判。 - 格式化:为这段注释单独维护一套作用域栈,用「注释起始列 + 注释内相对层级」定位每行,
与真实代码的括号状态完全隔离 —— 注释里故意留的未闭合
{不会影响外层缩进。 - 只有结束符(或结束符前没有代码)的行回到注释起始列。
/*void ParameterReset(void) 关闭时:所有行被压平 打开后:
{ /*void ParameterReset(void) /*void ParameterReset(void)
static INT16U i = 0; { {
INT8U ResultTemp = 0; static INT16U i = 0; static INT16U i = 0;
}*/ }*/ }*/
默认关闭:它会改变大段历史注释的观感,且"像不像代码"是启发式判断,建议先
dryRun看一遍报告再决定。
容错设计:即使词法跟踪误判,最坏结果只是某一行缩进偏了,绝不会破坏代码语义。
三、安装与使用
方式 A:开发模式运行(推荐先用这个验证)
- 用 VSCode 打开本仓库根目录(
A5E-A_MB_APP_V00_00_01_20230829)。 - F5 选择启动配置
运行插件(扩展开发宿主)。 - 在新弹出的窗口里打开被格式化的工程,右键批量格式化。
- 建议先把设置
cBulkFormatter.dryRun打开跑一遍,只出报告不写盘,确认无误再关掉。
方式 B:安装到本机 VSCode
见 第九节「导出(打包 .vsix)」,一步到位:
cd tools/vscode-c-bulk-formatter
npm run vsix && npm run install:local
触发方式(右键)
- 资源管理器:在文件或文件夹上右键 →
批量格式化 (C 语言);支持多选。 - 编辑器标签页:在标签上右键 →
批量格式化 (C 语言)(同时注册了editor/title/context兼容旧版)。 - 编辑器内选中的代码:在编辑器里右键 →
格式化选中的代码(当前选区)(详见下一节)。 - 命令面板:
C Bulk Formatter: 批量格式化整个工作区。
选区格式化(只格式化选中部分)
在编辑器里选中若干行 → 右键 → 格式化选中的代码(当前选区),
或用命令面板执行 C Bulk Formatter: 格式化选中的代码(当前选区)
(未选中任何内容时等价于格式化整篇,用来"就地格式化当前文件"很方便)。
与批量格式化的区别 —— 它是"就地改缓冲区",不是"批量改文件":
| 选区格式化 | 批量格式化 | |
|---|---|---|
| 改动对象 | 编辑器缓冲区(保存才落盘) | 磁盘文件 |
| 撤销 | Ctrl+Z 直接撤销 | 靠 .bak/ 备份 |
| 备份 | 不需要(有撤销) | 自动 .bak/ |
| 编码 | 不做识别与转换(缓冲区已是解码文本) | 识别 GBK/UTF-8 并转 UTF-8 |
| dryRun | 不适用 | 适用 |
| 脏文件 | 无所谓(本来就是改缓冲区) | 有未保存修改会跳过 |
| 缩进状态 | 对整篇推演后只写回选中的行,所以选区在深层嵌套里也能拿到正确起始层级 | 整篇重排 |
想加个快捷键的话,在
keybindings.json里加:{ "key": "ctrl+k ctrl+alt+f", "command": "cBulkFormatter.formatRange", "when": "editorTextFocus && editorHasSelection" }
视觉反馈
- 右下角通知区进度条:显示
(i/n) 文件名,可点击取消(已处理的文件保留)。 - 状态栏左侧:
C 批量格式化 i/n实时计数。
四、报告
- 输出位置:
<工作区根>/.c-bulk-formatter/report-YYYYMMDD-HHmmss.md(无工作区时落到扩展存储目录)。 - 内容:插件版本、本次配置快照、汇总统计、需人工确认(编码)清单、逐文件明细、跳过/失败清单。
- 除报告外,还会写入
C Bulk Formatter输出面板(可在「输出」下拉中选择)。 - 结束时:无异常自动打开报告;有异常弹警告并提供「打开报告」按钮。
五、设置项
| 设置 | 默认 | 说明 |
|---|---|---|
cBulkFormatter.indentSize |
4 |
缩进宽度 |
cBulkFormatter.tabSize |
4 |
制表符展开宽度 |
cBulkFormatter.insertSpaces |
true |
用空格缩进(关闭则用制表符) |
cBulkFormatter.keepBom |
false |
输出是否保留 BOM |
cBulkFormatter.formatCodeInComments |
false |
识别被 /* */ 注释掉的代码块并一起整理缩进 |
cBulkFormatter.onEncodingError |
skip |
编码异常处理方式 |
cBulkFormatter.dryRun |
false |
干跑:只出报告不写盘 |
cBulkFormatter.extensions |
.c .h .cpp .hpp .cc .hh .inc .s .a51 |
参与处理的扩展名 |
cBulkFormatter.exclude |
node_modules / .git / Objects / Listings / *.o / *.d / *.crf |
排除模式(Keil 中间产物) |
cBulkFormatter.backup |
true |
写盘前备份原始文件(已存在则不覆盖,始终保留最原始版本) |
cBulkFormatter.backupMode |
central |
central=统一收集到独立备份文件夹;sibling=旧行为(源文件旁 <文件名>.bak) |
cBulkFormatter.backupDirName |
.bak |
central 模式下备份文件夹名 |
cBulkFormatter.backupVersioning |
first |
first=只留最原始一份(体积最小);timestamp=每轮归档到 YYYYMMDD-HHMMSS/ 批次目录 |
cBulkFormatter.backupKeepBatches |
5 |
timestamp 模式下保留最近 N 批,超出部分在运行结束后自动清理;0=不自动清理 |
cBulkFormatter.openReportOnFinish |
true |
无异常时自动打开报告 |
六、安全机制
- 仅改行首空白:写盘内容由「新缩进 + 原行内容(去行尾空白)」拼成。
- 内容无变化不写盘:逐字节比对,未变化不触发写事件。
- 统一备份:默认把原始文件收集到
<工作区根>/.bak/<原始相对路径>(镜像目录结构),源目录不会被.bak文件污染; 已存在同名备份则不覆盖,始终保留最原始版本;备份失败则跳过该文件,绝不做「无备份写盘」。- 备份目录内的文件会被
exclude默认排除,并在代码里二次校验(isNoisePath),不会被再次格式化。 - 想要旧行为:
cBulkFormatter.backupMode = sibling。
- 备份目录内的文件会被
- 脏文件跳过:编辑器里有未保存修改的文件直接跳过,避免与缓冲区冲突。
- 干跑模式:先出报告,确认后再真正执行。
- 可取消:进度条点取消,已完成的文件已落盘,剩余不动。
备份目录长什么样
A5E-A_MB_APP_V00_00_01_20230829/ <- 工作区根(git 仓库根)
├── app/
│ └── main.c <- 源文件,旁边干干净净
├── bsp/
│ └── uart.c
└── .bak/ <- 统一备份目录
├── app/
│ └── main.c <- 首次格式化前的原始内容
└── bsp/
└── uart.c
已默认写入 .gitignore(**/.bak/),不会被提交。
清理历史遗留的散落 .bak
用命令清理(推荐):命令面板 Ctrl+Shift+P → C Bulk Formatter: 清理备份,
选中「清理源目录里散落的 <文件名>.bak」,会先列出数量与体积,二次确认后才删。
命令行等效操作:
# 预演:只列出会删什么
node tools/vscode-c-bulk-formatter/cli.js --cleanup scattered --root .
# 确认后真删
node tools/vscode-c-bulk-formatter/cli.js --cleanup scattered --root . --yes
不想用命令也行,git bash 里手工清理:
find . -name '*.bak' -not -path './.bak/*' -not -path './.git/*' # 先看
find . -name '*.bak' -not -path './.bak/*' -not -path './.git/*' -delete # 再删
备份体积治理
两种版本策略:
| 策略 | 目录结构 | 体积 | 适用 |
|---|---|---|---|
first(默认) |
.bak/app/main.c |
最小 —— 每个文件只留最原始一份,反复运行不再增长 | 只要"能回滚到格式化前" |
timestamp |
.bak/20260918-153000/app/main.c |
每轮一个新批次,可回溯到任意一轮之前 | 需要多版本对比/追责 |
timestamp 模式下,每次运行结束会自动清理超出 backupKeepBatches(默认 5)的最旧批次,
所以体积是有上界的 —— 这也是"治理"的核心。
一键清理命令 C Bulk Formatter: 清理备份 提供四种动作(都会先报数量/体积,再二次确认):
| 动作 | 说明 |
|---|---|
清理源目录里散落的 <文件名>.bak |
旧版本遗留的污染,源目录恢复干净 |
| 清理孤儿备份(源文件已删除或改名) | first 模式下最容易堆积的一类 —— 源文件删了,备份还留着 |
| 清理超出保留份数的旧批次 | 手动触发一次 backupKeepBatches 裁剪 |
| 清空整个备份目录 | 危险操作,需要二次确认 |
七、命令行(CI / 批处理)
cli.js 与 VSCode 命令共用同一套流水线,可在 CI、脚本或没有 VSCode 的环境里跑:
cd tools/vscode-c-bulk-formatter
# 干跑(只出报告不写盘)
node cli.js ../../../app ../../../bsp --root ../../../ --dry-run
# 真跑(带备份)
node cli.js ../../../app ../../../bsp --root ../../../
# 常用开关
node cli.js app --root . --indent 4 --tab 4 --encoding byte-safe
node cli.js app --root . --format-comments # 注释内代码也格式化
node cli.js app --root . --versioning timestamp --keep-batches 3
node cli.js --cleanup orphans --root . # 预演
node cli.js --cleanup orphans --root . --yes # 执行
退出码:0 成功,1 有失败项,2 参数错误 —— 可直接用于流水线卡点。
八、自测
node tools/vscode-c-bulk-formatter/test/run-test.js
覆盖:编码识别(ASCII/UTF-8/GBK/BOM/UTF-16/二进制)、事故回归用例 (疑似 UTF-8 带零星坏字节绝不整体转 GBK;真 GBK 即使"非法占比"偏低也要认出)、 字节安全通道「非 ASCII 字节一字节不变」、缩进(块/case/注释/预处理/续行)、 制表符「字符串/字符常量内部保持原样」、GBK+制表符端到端、二次格式化幂等性、 备份路径解析(central/sibling/timestamp/自定义目录名/无工作区退化)、 备份落盘「二次不覆盖最原始版本」、批次保留策略与真实目录的清理(散落/孤儿/旧批次)、 注释内代码格式化(含"不污染外层缩进"与 switch/case 一致性)、报告版本号、 选区格式化(范围外不动 / 深层嵌套起始状态 / switch 选区 / 越界收敛 / 非 ASCII 不受影响)。 当前共 62 项。
九、目录结构
tools/vscode-c-bulk-formatter/
├── extension.js # 入口:命令注册、目标收集、进度、报告调度
├── cli.js # 命令行入口(与插件共用同一套流水线,可用于 CI)
├── src/
│ ├── encoding.js # 步骤1 编码识别与转换(最保守策略)
│ ├── tabspace.js # 步骤2 制表符 -> 空格(保护字符串内容)
│ ├── indent.js # 步骤3 缩进重排(只改行首空白)
│ ├── backup.js # 步骤4 备份路径解析与落盘(集中收集 + 批次版本)
│ ├── cleanup.js # 备份清理与体积治理
│ ├── processor.js # 步骤1~3 流水线(纯函数,可离线单测)
│ └── report.js # 报告与日志
├── test/run-test.js # 离线自测
└── package.json
十、导出(打包 .vsix)
.vsix 是 VSCode 扩展的分发格式 —— 单文件、可离线安装。这是内网/团队分发最省事的方式。
cd tools/vscode-c-bulk-formatter
# 1) 先自测(可选但推荐)
npm test
# 2) 打包
npm run vsix
# 等价于:npx --yes @vscode/vsce package --no-dependencies
# 产物:c-bulk-formatter-0.0.1.vsix (约 20 KB)
打包内容由 .vscodeignore 控制,当前排除 test/、scripts/、.vscode/、node_modules/、*.vsix。
安装 .vsix 的三种方式
# 方式 1:脚本自动装(会依次尝试 code.cmd / code)
npm run install:local
# 方式 2:命令行
code --install-extension c-bulk-formatter-0.0.1.vsix --force
方式 3:VSCode → 扩展(Ctrl+Shift+X)→ 右上角 ... → 从 VSIX 安装... → 选文件。
--force用于同版本号覆盖安装(改完代码重新打包时很有用)。 若code不在 PATH:VSCode 里按Ctrl+Shift+P→Shell Command: Install 'code' command in PATH。
纯文件夹方式(不打包也能用,最土但最快)
把整个 vscode-c-bulk-formatter 目录复制到
%USERPROFILE%\.vscode\extensions\c-bulk-formatter-0.0.1\,重启 VSCode 即可(没有即改即用,仍需重启)。
注意:目录名必须是 <name>-<version> 的格式,且里面要直接是 package.json。
十一、发布
10.1 发布前必改的三处
| 位置 | 当前值 | 改成 |
|---|---|---|
package.json → publisher |
local |
你在 Marketplace 注册的 Publisher ID |
package.json → repository.url |
占位 URL | 真实的 Git 仓库地址(vsce publish 会校验) |
README.md |
—— | 发布页正文,首屏决定别人装不装 |
(可选)加图标:放一个 128×128 的 icon.png 并在 package.json 加 "icon": "icon.png"。
10.2 路线 A:VSCode Marketplace(公开,全球可见)
准备:
- 注册 Azure DevOps 组织:https://dev.azure.com。
- 生成 PAT:User settings → Personal access tokens → Scopes 勾选
Marketplace: Manage(其余可不勾)。记下 token。 - 创建发布者:https://marketplace.visualstudio.com/manage → Create publisher → 得到 Publisher ID。
发布:
# 首次:用 PAT 登录(交互式输入 token)
npx --yes @vscode/vsce login <publisher-id>
# 发布当前 version
npm run publish
# 等价于:npx --yes @vscode/vsce publish --no-dependencies
# 或一步到位:自动升版本号 + 发布
npm version patch && npm run publish
npm version minor && npm run publish
# 非交互(CI 场景)
npx --yes @vscode/vsce publish --no-dependencies -p <PAT>
常用变体:
# 只发布不上架首页(有人知道链接才能搜到)
npx --yes @vscode/vsce publish --no-dependencies --unpublished
# 下架 / 删除某个版本
npx --yes @vscode/vsce unpublish <publisher-id>.c-bulk-formatter
注意:Marketplace 的扩展默认都是公开的。要「私有」,只能走 Azure DevOps 组织的私有 Marketplace(需付费/企业订阅), 内网团队一般直接用「发 .vsix」代替。
10.3 路线 B:Open VSX(开源替代,VSCodium / Gitpod / Theia 用这个)
# 先注册账号并生成 token:https://open-vsx.org
npx --yes ovsx publish c-bulk-formatter-0.0.1.vsix -p <OVSX_TOKEN>
10.4 路线 C:不发布,只内部分发(推荐给你们现状)
npm run vsix产出c-bulk-formatter-0.0.1.vsix;- 放到共享盘 / Git 附件 / 公司内部制品库;
- 同事
code --install-extension c-bulk-formatter-0.0.1.vsix; - 升级时把
version加一(npm version patch)重新打包,安装时带--force。
10.5 发布流程自检清单
- [ ]
npm test全绿 - [ ]
version已递增(Marketplace 不允许同版本重复发布) - [ ]
publisher/repository已改为真实值 - [ ]
README.md无本地绝对路径、无内部敏感信息 - [ ]
npx --yes @vscode/vsce ls看一遍将被打包的文件清单 - [ ] 先在干净环境(或另一台机器)装一次 .vsix 验证可用
- [ ] 用
dryRun=true在真实工程上跑一遍,确认报告符合预期
十二、后续版本可做(本期不做)
- 空格对齐(
=、//注释列对齐、结构体成员对齐) - 空行策略(函数间空行、连续空行压缩)
- 长行折行、行宽限制
- 与
.editorconfig/ Keil 配置互通 - 目录级配置文件与增量缓存