Skip to content
| Marketplace
Sign in
Visual Studio Code>Formatters>C Bulk Formatter (C 批量格式化)New to Visual Studio Code? Get it now.
C Bulk Formatter (C 批量格式化)

C Bulk Formatter (C 批量格式化)

jack20240738

|
1 install
| (0) | Free
C 语言批量格式化:编码识别转 UTF-8、制表符转空格、缩进重排,支持资源管理器与标签页右键触发。
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

C Bulk Formatter(C 批量格式化插件)

面向 C / 嵌入式工程的格式化插件,主打批量与安全:

  • 编码:自动识别 GBK / UTF-8 并统一转成 UTF-8;无法 100% 确认就绝不动手
  • 制表符:按制表位展开为空格,字符串 / 字符常量内部的 \t 保持原样
  • 缩进:只重写行首空白,行内字符一个都不改;覆盖 {}、switch-case、注释星号对齐、预处理指令、宏续行
  • 备份与清理:原始文件集中备份到 .bak/,支持批次版本归档与一键清理
  • 多种入口:资源管理器 / 标签页右键批量、编辑器选区就地格式化、命令行(可进 CI)

设计原则:宁可漏做,绝不误改。

一、处理流程(严格按顺序)

读文件 -> [1] 编码识别/转 UTF-8 -> [2] 制表符转空格 -> [3] 缩进重排 -> [4] 备份(统一到 .bak) -> 写盘 -> 生成报告
步骤 模块 说明
1 编码识别与转换 src/encoding.js 识别 UTF-8 BOM / UTF-8 / GBK / 纯 ASCII,统一输出 UTF-8;无法 100% 确认时绝不转码,记入报告并弹窗提示人工确认
2 制表符整理 src/tabspace.js 按制表位把 \t 展开为空格(视觉对齐不变,字符串/字符常量内部除外),同时清理行尾空白
3 缩进重排 src/indent.js 只重写行首空白,行内字符一个不动
4 备份与写盘 src/backup.js + src/cleanup.js 原始文件统一收集到工作区根的 .bak/ 目录(按原始相对路径镜像),源目录保持干净;支持版本归档与一键清理

编码判定规则(最保守)

只有在「100% 确认」时才转码,否则一律不动。判定顺序:

  1. UTF-8 BOM → UTF-8;UTF-16 BOM → 跳过(本插件不处理 UTF-16)
  2. 纯 ASCII → UTF-8
  3. 严格 UTF-8 校验通过 → 原样按 UTF-8 处理(恒等变换,字节不变)
  4. 有非法字节时,先做 GBK 正面确认(比"坏字节占比"可靠得多):
    • 自研 validateGbkStructure() 逐字节校验 GBK 结构(0x81~0xFE + 0x40~0x7E/0x80~0xFE), 比 TextDecoder 严格 —— WHATWG 的 gbk/gb18030 解码器会静默接受单个 0xFF 这类垃圾字节
    • 再用 fatal:true 严格解码,且结果不得含控制字符或 U+FFFD
    • 且非法序列数 ≥ 4(只有一两处坏字节的不算"整篇 GBK",宁可不动)
    • → 全部通过才算 GBK,转成 UTF-8
  5. 不满足 GBK 条件,但非法字节占比 ≤ 50%(即"基本是合法 UTF-8,只有零星坏字节") → 判为 utf8-damaged,走字节安全通道:把文件当裸字节,只改空白,非 ASCII 字节一个都不动
  6. 其余 → unknown,直接跳过不写盘(很可能是二进制或被破坏的文件)
  7. 含 NUL 字节 → 判为二进制,跳过

为什么阈值取得这么"偏":两种误判的代价完全不对称。 把 GBK 误判成"UTF-8 带坏字节"只是不转码(文件原样保留,安全); 把 UTF-8 误判成 GBK 会让整篇中文变成乱码(不可接受)。 所以宁可漏转、绝不误转。

字节安全通道还有一道硬校验:处理前后「去掉空白后的字节序列」必须逐字节相同(integrityOk), 一旦不成立立刻阻止写盘。

编码策略 onEncodingError(可在设置中改)

值 行为
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 后,插件会:

  1. 识别:向后续扫描到注释结束,若注释内容去空行后 ≥3 行且 60% 以上像代码 (以 {/}/; 结尾、以 C 关键字开头、赋值、函数调用、i++ 等)→ 判定为"被注释掉的代码"; 散文注释(说明文字)不会被误判。
  2. 格式化:为这段注释单独维护一套作用域栈,用「注释起始列 + 注释内相对层级」定位每行, 与真实代码的括号状态完全隔离 —— 注释里故意留的未闭合 { 不会影响外层缩进。
  3. 只有结束符(或结束符前没有代码)的行回到注释起始列。
/*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:开发模式运行(推荐先用这个验证)

  1. 用 VSCode 打开本仓库根目录(A5E-A_MB_APP_V00_00_01_20230829)。
  2. F5 选择启动配置 运行插件(扩展开发宿主)。
  3. 在新弹出的窗口里打开被格式化的工程,右键批量格式化。
  4. 建议先把设置 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 无异常时自动打开报告

六、安全机制

  1. 仅改行首空白:写盘内容由「新缩进 + 原行内容(去行尾空白)」拼成。
  2. 内容无变化不写盘:逐字节比对,未变化不触发写事件。
  3. 统一备份:默认把原始文件收集到 <工作区根>/.bak/<原始相对路径>(镜像目录结构),源目录不会被 .bak 文件污染; 已存在同名备份则不覆盖,始终保留最原始版本;备份失败则跳过该文件,绝不做「无备份写盘」。
    • 备份目录内的文件会被 exclude 默认排除,并在代码里二次校验(isNoisePath),不会被再次格式化。
    • 想要旧行为:cBulkFormatter.backupMode = sibling。
  4. 脏文件跳过:编辑器里有未保存修改的文件直接跳过,避免与缓冲区冲突。
  5. 干跑模式:先出报告,确认后再真正执行。
  6. 可取消:进度条点取消,已完成的文件已落盘,剩余不动。

备份目录长什么样

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(公开,全球可见)

准备:

  1. 注册 Azure DevOps 组织:https://dev.azure.com。
  2. 生成 PAT:User settings → Personal access tokens → Scopes 勾选 Marketplace: Manage(其余可不勾)。记下 token。
  3. 创建发布者: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:不发布,只内部分发(推荐给你们现状)

  1. npm run vsix 产出 c-bulk-formatter-0.0.1.vsix;
  2. 放到共享盘 / Git 附件 / 公司内部制品库;
  3. 同事 code --install-extension c-bulk-formatter-0.0.1.vsix;
  4. 升级时把 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 配置互通
  • 目录级配置文件与增量缓存
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft