
简介Geany编辑器的JSON Prettifier插件以完整源码包形式提供面向Linux平台下使用Geany的开发者与进阶用户解决JSON文件格式化混乱、难以阅读及校验繁琐等常见问题。插件具备一键美化与缩小格式能力支持全部或选中文本的部分处理缩进可用空格或制表符自定义还能按配置转义正斜杠它同样支持在一个文件中拆分多个独立JSON实体分别格式化灵活度较高。模块源码结构较完整依赖Geany、GTK3/2、yajl等组件包内176个文件涵盖.c/.h核心源码、JSON与gold测试样例、CMake脚本、configure与makefile构建文件以及构建与使用说明文档压缩包整体仅163KB便于快速部署和二次开发。目前已有402人学习浏览适合希望深入理解Geany插件机制、学习JSON解析与格式化实现或需要一款轻量JSON处理工具的C语言开发者下载参考。1. 为什么要在 Geany 里直接做 JSON 格式化一个插件省掉浏览器开关用 Geany 写配置文件、调接口数据的人多半经历过这个瞬间从后端复制回一段压缩成单行的 JSON手工缩排、找括号、补逗号改到怀疑人生。Geany-JSON-Prettifier 就是给这类场景设计的编辑器插件把 JSON 的格式化美化器、缩小压缩成单行和验证语法合法性检查直接塞进 Geany 的菜单与快捷键不切浏览器、不打开在线工具。适合常年在轻量编辑器里处理 JSON 文件、JSON 数组样例和接口返回数据的开发者。下面按我自己的落地路径来讲装好、调顺、用出效率。2. 插件的运行骨架与 JSON 状态机先讲清原理再动手写代码插件和脚本工具最大的区别在于生命周期。脚本是一次性运行插件要长期住在编辑器进程里响应菜单点击、快捷键和文档切换。Geany 从 1.36 版本开始全面使用新版插件 API要求插件导出geany_load_module符号在里头完成版本声明、回调注册和菜单挂靠。下面这段是这类插件的常见骨架不是某份官方源码是我按 Geany 插件规范写出来的最小结构。#include geanyplugin.h #include json-glib/json-glib.h /* Geany 装载 .so 时会调用这个符号 */ void geany_load_module(GeanyPlugin *plugin, GeanyData *data) { /* 基本信息在插件管理器里显示 */ plugin-info-name Geany-JSON-Prettifier; plugin-info-description Format / Minify / Validate JSON; plugin-info-version 1.0; plugin-info-author local build; /* 生命周期回调init 做菜单注册cleanup 释放资源 */ plugin-funcs-init json_prettifier_init; plugin-funcs-cleanup json_prettifier_cleanup; }先说明两个关键点。第一data指针在整个插件生命周期里一直有效文档切换、编辑器窗口操作都靠它取当前文档对象所以 init 阶段要把它保存到全局变量第二info-version和 Geany 内部的插件 API 版本是两回事API 版本匹配由 Geany 在装载时自动检测装上去不认识多半是编译时头文件版本和运行版本不一致这一点我会在第 5 章单独展开。2.1 插件装载机制从 .so 文件到插件管理器的完整链路Geany 对第三方插件的处理很像一套微型包管理编译产物是一个动态库放在用户级插件目录~/.config/geany/plugins/下面Geany 启动时扫描这个目录逐个dlopen并调用导出符号。系统级插件则放到/usr/lib/geany/但日常开发我建议一律走用户级目录因为不需要 root 权限升级 Geany 后重编插件也不影响系统其他组件。插件管理器里看到的“启用/禁用”实际是控制 Geany 是否调用该插件的 init 回调。禁用并不是卸载文件只是不给它注册菜单和快捷键。理解这条链路对排查问题很重要如果插件管理器里压根看不到插件问题通常出在装载阶段也就是动态库依赖缺失或符号未被导出如果看得到但菜单不出现那才是 init 回调里的注册逻辑出错。装载完成后Geany 会调用插件注册的 init 函数。常见的注册动作有三个在“工具”菜单里加子菜单、向 Geany 申请一组按键绑定keybinding、设置一个用于响应文档保存事件的回调。格式化、缩小、验证这三个动作我一般绑定到CtrlShiftF、CtrlShiftM、CtrlShiftV附近避免和 Geany 自带的查找、粘贴快捷键冲突。2.2 为什么格式化不能按行处理字符串与转义的状态机初写格式化插件最常犯的错是把 JSON 当普通文本逐行处理。问题在于 JSON 的字符串字面量里可以包含花括号、方括号、冒号和逗号——这些都是决定缩进结构的符号但出现在字符串内部时它们只是普通字符。更麻烦的是转义序列{\key\:1}这串内容里最外层的引号配对必须靠转义判断按行扫描必然出错。所以格式化、缩小、验证这三个操作必须共享同一个词法分析底座逐个字符扫描维护一个“当前是否在字符串内”的状态。遇到就翻转状态在字符串内遇到\\要跳过下一个字符避免把\当成字符串结束。我把这个判断抽成一个状态机函数三个功能各用各的消费逻辑但词法部分只写一遍。def tokenize(raw): i, n 0, len(raw) in_str False while i n: c raw[i] if c and (i 0 or raw[i-1] ! \\): in_str not in_str yield (STRING_QUOTE, i, c) elif c \\ and in_str: yield (ESCAPE, i, c) i 1 elif not in_str and c in {}[],:: yield (PUNCT, i, c) else: yield (CHAR, i, c) i 1这个生成器给后续格式化提供了足够信息PUNCT类型才能触发缩进变化STRING_QUOTE决定字符串边界ESCAPE防止误判转义引号。参数上需要注意判读前一个字符是不是\的写法在出现连续反斜杠时会翻车比如\\这种场景正确做法是从当前位置向前数反斜杠的个数奇数个说明引号被转义。这里用raw[i-1] ! \\是教学简化真实插件里我写的是回看计数。2.3 作用范围参数全文档处理与选区处理的切换格式化类功能必须回答一个产品问题用户选中了一段是处理选中部分还是整个文件我的做法是“智能切换”sci_get_selection_start和sci_get_selection_end返回光标位置两者相等说明没有选区处理整个文档不等则只处理选区。缩小时的逻辑更保守只处理全文档因为把碎片化的选区压缩成单行再拼回原文很容易破坏结构。Geany 封装了 Scintilla 编辑器组件操作当前文档的标准路径是拿GeanyDocument对象再通过sci_get_text取全文、sci_set_text写回。这里有个细节写回前要把光标行列位置记下来格式化后按原位置重新定位否则用户每按一次快捷键光标就跳回文件头体感很差。后续章节里我会继续给出每个功能的参数表和坑点先记住一个原则——格式化必须可逆、可预期用户按一次快捷键得到的是结构更清晰的同一份内容而不是被打乱的光标和视图。3. 编译安装与装载验证从 Makefile 到 ~/.config/geany/plugins装这个插件不是下载一个安装包双击完事而是编译一个 C 动态库再放进 Geany 的插件目录。好处是你能顺着编译过程理解插件和 Geany 的耦合点头文件版本、依赖库路径、符号导出。坏处是环境差一点就翻车。这一章给出我验证过的编译安装路径按顺序走十分钟能跑通。3.1 编译环境准备三个依赖包缺一不可编译 Geany 插件需要三类东西Geany 自身头文件、GTK/GLib 开发文件、一个 JSON 解析库。Geany 用 GTK 做界面插件菜单和消息窗口都依赖 GTK 类型JSON 解析我用的是 glib 生态里常见的 json-glib它提供JsonParser、JsonNode和生成器格式化时直接操作节点树比手写缩进算法省一半工作量。Debian/Ubuntu 系的常见安装命令是这样不同发行版包名略有差异但geany-dev或libgeany-dev二选一总会有一个存在# Debian/Ubuntu 常见包名按你发行版实际仓库为准 sudo apt install geany geany-dev libjson-glib-dev \ libgtk-3-dev libglib2.0-dev pkg-config --modversion geany gtk-3.0 json-glib-1.0最后一行pkg-config --modversion是体检命令三个版本都能打印出来才说明开发头文件装齐了。我建议把输出的 Geany 版本记下来一会儿编译完拿它和geany --version对比。两个值不一致装完插件必现兼容性报错。3.2 最小 Makefile 构建两条命令产出 .so我不喜欢用完整的 autotools 工程来伺候一个单文件插件Makefile 反而更透明。核心是把 pkg-config 提供的编译参数接进来加上-fPIC生成位置无关代码动态库必需和-shared链接成共享库CC ? gcc PLUGIN geany-json-prettifier.so SRC src/geany-json-prettifier.c GEANY_CFLAGS : $(shell pkg-config --cflags geany gtk-3.0 json-glib-1.0) GEANY_LIBS : $(shell pkg-config --libs geany gtk-3.0 json-glib-1.0) all: $(PLUGIN) $(PLUGIN): $(SRC) $(CC) -fPIC -shared -o build/$(PLUGIN) $(SRC) \ $(GEANY_CFLAGS) $(GEANY_LIBS) -Wl,-z,defs install: all mkdir -p ~/.config/geany/plugins install -m 0644 build/$(PLUGIN) ~/.config/geany/plugins/执行时先建 build 目录再make然后make install就完成安装了。-Wl,-z,defs是我个人的执念它要求所有外部符号必须显式链接防止某些缺依赖的函数在运行时才崩溃。加了它以后链接阶段就会直接报出缺失的库把问题提前到编译期。mkdir -p build make make install装完后~/.config/geany/plugins/下应该能看到geany-json-prettifier.so。注意这个目录只认.so后缀放过.so.1或带调试信息的长文件名都会直接忽略。3.3 装载结果验证命令行与插件管理器双重确认Geany 提供命令行工具geany --list-plugins列出所有扫描到的插件这是最快验证装载的方式。程序尚未启动所以即使 init 回调有逻辑错误这里也能先确认动态库被成功解析geany --list-plugins | grep -i json如果这行输出为空先geany -v看启动日志里的dlopen报错八成是依赖库找不到。如果能看到插件项再启动 Geany 进入“工具 → 插件管理器”勾选启用。插件管理器的列表来自启动时扫描运行中途新拷贝进来的 .so 不会自动出现需要重启 Geany。开发迭代时这是最常见的时间浪费改了代码、重新编译、拷进目录菜单还是老样子其实只是没重启。重启后还有一个验证点Geany 的“工具”菜单是否多出 JSON 格式化、缩小、验证三个条目。菜单没出现但插件已启用问题基本定位在 init 回调里注册菜单的代码比如菜单路径拼错。我会在下一章把这三种功能的触发路径和参数行为讲清楚。4. 格式化、缩小、验证三件套功能划分与参数这样调插件装上只是开始真正决定好不好用的是三个功能的边界和参数。很多人以为“格式化和缩小的关系就是反过来”实际没那么简单。格式化可以无损还原语法结构缩小却不是简单删空格验证则要输出人能读懂的报错。这一章逐个讲清楚每个功能的行为约定和偏好参数。4.1 格式化器缩进宽度、尾随换行与键排序格式化器做的事情是解析 JSON 文本按节点层级输出缩进。json-glib 的序列化器直接支持缩进参数但默认的 2 空格只是一个起点三个参数我建议务必暴露出来参数默认值说明缩进宽度2 空格可选 2 / 4 / Tab团队规范不同尾随换行保留关闭后文件末尾不留空行适合拼接口拼接场景键排序关闭按 key 字典序重排开启后适合比对两个 JSON 差异保留 Unicode开启关闭时中文字符被转成\uXXXX文件体积变大但兼容旧系统键排序是我特别建议勾选的一个选项。排查接口返回差异时把两份 JSON 都开排序再格式化diff出来的结果会干净很多不开排序同样内容只是字段顺序不同diff 就是一大片红色。代价是排序会打乱原始顺序如果对方接口依赖字段顺序这个选项要由消费方确认再开。格式化在写回前必须先验证一次语法这是这个插件的隐含约定格式化的前提是内容合法。非法内容直接弹错误信息到消息窗口不覆盖用户原文。我见过不少同类插件格式化非法文件时输出一团乱码这个前置验证是最值得保留的设计。4.2 缩小器字符串防护与转义回看的边界处理缩小器目标是把文件体积压到最小核心策略是删除字符串外部的空白字符。听起来简单真正考验的是字符串防护JSON 字符串里的空格是数据的一部分hello world里的那个空格不能动。前面第 2 章讲的状态机在这里派上用场缩小时遇到引号进入字符串模式字符原样拷贝退出字符串后空白才被丢弃。转义回看是最容易翻车的点。字符串a\\b里第二个前有一个反斜杠但从右往左数有两个反斜杠所以这个引号没有被转义它才是字符串的结束符。实现时不能只看紧邻前一个字符要从当前位置向左连续数反斜杠个数奇数个才是转义引号。这个细节决定了缩小器是安全压缩还是破坏数据。static gboolean is_escaped(const char *s, int pos) { int backslashes 0; /* 从 pos-1 向左数连续反斜杠 */ for (int i pos - 1; i 0 s[i] \\; i--) backslashes; return backslashes % 2 1; }这段 C 代码的核心逻辑是从右往左数连续反斜杠奇数个说明当前引号被转义、属于字符串内部符号偶数个说明它是真正的字符串边界。参数说明pos是当前扫描到的引号下标s是全文缓冲区复杂度 O(n)整个缩小过程只会对总字符数做一次扫描单次开销可忽略。真正要留意的是缓冲区大小参数C 字符串以\0结尾JSON 原文里如果本身含有\u0000处理时要按长度遍历而不是strlen。缩小器的输出我默认不追加尾随换行省掉的最后一个字节在某些场景下有仪式感。另外缩小后的单行 JSON 在 Geany 里可能触发长行性能问题Scintilla 对超长行有折行机制插件不必干预但要知道这是编辑器的折叠行为不是文件损坏。4.3 验证器错误定位到行列并高亮现场验证器是整个插件的门卫。它的工作不只是“能解析通过吗”而是“错了错在哪一行哪一列预期是什么”。json-glib 的 parser 会抛出一个带行号和列号的错误插件要做的是把解析器的抽象报错翻译成人话再在 Geany 的消息窗口输出同时用 Scintilla 的标记indicator把出错位置高亮成红色。一个典型的输出长这样JSON 验证失败 第 12 行第 8 列期望 , 或 ]但实际读到 } 第 12 行内容 itemsz: [1, 2 3}注意第二行内容我建议原样截出来比只报行列有用得多。同行里多个错误只报第一个这是刻意的JSON 解析器一旦语法错乱后续所有错误都是连锁反应全部罗列只会淹没真实问题。修完第一个再跑一次是验证器的工作方式。验证器还有一个隐藏功能确认文件按 JSON 标准解析而不是 JavaScript 对象字面量。JSON 标准不允许注释、不允许尾随逗号、键必须加双引号。很多人从 JS 里把对象字面量直接粘进 JSON 文件验证器报错后第一反应是“插件坏了”其实是标准差异。这类误报场景我放在下一章避坑里展开。5. 避坑手记插件不装载、大文件卡死、中文乱码与报错错位这个插件本身逻辑不复杂真正劝退用户的坑全在集成环境里。我把自己踩过的和同事踩过的典型问题整理成四条记录按“现象 → 原因 → 解决”的格式写出来遇到同类问题直接对号入座。5.1 插件管理器里看不到插件项现象geany --list-plugins输出为空插件目录里明明有.so文件。打开 Geany 的启动日志终端里跑geany -v会看到类似libgeany.so: undefined symbol或cannot open shared object file的行。原因通常是两类。一是编译用的 Geany 头文件版本和运行的 Geany 主程序版本不一致插件 API 结构体布局对不上dlopen阶段就失败二是 json-glib 运行时库缺失动态库依赖检查不过。常见做法是拿系统包管理装回与主程序同版本的开发包别从网上下一个来历不明的旧源码硬编。解决先geany --version记录运行版本再pkg-config --modversion geany对比编译版本两者对齐后重编。确认依赖则用ldd ~/.config/geany/plugins/geany-json-prettifier.so看有没有not found。我每次重装系统后第一件事就是把这两个版本号打出来省得排查半天才发现是最低级的不匹配。5.2 大文件格式化整个界面卡死几分钟现象把一个十几 MB 的压缩 JSON 全选后按格式化Geany 界面冻结光标转圈过一会儿才恢复期间无法切文件也无法保存。原因格式化在主线程同步执行解析和序列化都占用单线程超大 minified 文件等于一次性做词法扫描加上整树序列化Scintilla 写回时还要重排全文布局。十几 MB 文本在 GTK 的文本组件里本身就是重量级操作。解决这类操作不应该进编辑器插件。我的实际习惯是把文件交给外部工具处理——jq . file.json out.json或者python3 -m json.tool命令行工具没有 UI 线程负担还能再加--tab参数指定缩进。插件的格式化功能保留给日常中小文件超过 5 MB 的 minified 文件默认走外部工具这是边界意识不是功能缺失。5.3 格式化后中文变乱码或被转成 \uXXXX现象原文是正常的中文格式化后变成一串\u5b57或者直接显示成乱码方块。前者是 ASCII 化开关被打开后者多半是文件编码判定错。原因插件内部使用 json-glib 时序列化器默认做 UTF-8 输出。如果 Geany 把文件按 GBK 判定插件读到的字节流就不是合法 UTF-8解析阶段就失败强制按本地编码读时写回又破坏了 UTF-8 字节序列。\uXXXX则是保留 Unicode 参数被关掉中文全部转义了。解决在 Geany 的“文档 → 设置文件编码”里把文件明确设成 UTF-8保存后重新打开再格式化。插件参数里把“保留 Unicode”保持开启。这个坑我建议列入团队规范所有 JSON 文件统一 UTF-8 无 BOM插件只承担格式化职责编解码混用的问题永远在源头上消灭。5.4 尾随逗号和注释导致的报错位置错位现象JSON 文件里第 30 行有个多余的尾随逗号验证器却报告第 22 行“未预期的 }”或者文件里有//注释报错位置每次都在注释附近而不是真正有问题的业务字段。原因JSON 标准严格禁止注释和尾随逗号标准解析器遇到它们就中断错误位置其实是“状态机突然失配”的位置不是用户心中的“出错位置”。用户看到报错行检查那行发现没问题于是怀疑定位逻辑坏了。其实定位逻辑是对的只是 JSON 的语法设计要求错误传播是不精确的。解决插件在报错信息里追加提示文案“JSON 标准不允许注释或尾随逗号”并附上错误行内容。我建议用户先全局搜索//、/*、,}和, ]这四种模式手工清理完再跑验证器。插件层面做不到智能纠错这是设计边界验证器只负责指出语法不合规不负责猜测用户意图。5.5 格式化后光标跳回文件开头撤销还要按两次现象按下格式化快捷键文档内容排列好了但光标飞到第一行按一次 CtrlZ 没反应按两次才回退到原文。原因光标跳转是因为未保存光标位置撤销两次是因为插件先写回了一次文本又把光标定位操作做成了一次独立可撤销的编辑动作。两个问题同源对 Scintilla 编辑语义理解不到位。解决格式化前用sci_get_current_pos记录位置格式化后调用sci_goto_pos复位光标定位操作要放在同一次 begin/end undo 动作内部避免生成独立撤销步骤。这块代码量不大但直接影响体感。我复现验证的标准动作是把光标放在文件中间按快捷键看光标是否原地不动再按一次 CtrlZ看是否一步还原。6. 把插件融进日常流程快捷键绑定与外部工具互补插件默认提供菜单入口但频繁格式化时切鼠标很低效。Geany 的插件 API 支持注册按键绑定启用插件后进入“工具 → 插件管理器 → 按键绑定”能找到 Format JSON、Minify JSON、Validate JSON 三个条目。我的配置是CtrlShiftF格式化、CtrlShiftM缩小、CtrlShiftV验证跟 Geany 自带的CtrlF查找区分开冲突概率最低。注意按键绑定在插件重新编译后有时会丢需要重新设置一次。按键绑定适合光标正在当前文档里的场景但更省事的路径是让插件和外部命令互补。格式化超大文件时我常用 Geany 的“构建 → 设置构建命令”挂一个自定义命令# 使用 jq 做命令行格式化输出到临时文件避免误覆盖原文件 jq . $(basename $) /tmp/formatted.json mv /tmp/formatted.json $(basename $)这个命令适用于插件在处理大文件时力有不逮的边界场景。参数说明$(basename $)拿当前文件名输出到 /tmp 再覆盖回来防止 jq 失败时把原文件清空——这是命令行处理 JSON 最常见的破坏性事故。外部工具的缺点是没有错误定位高亮所以我的分工是日常编辑用插件超大文件和批量脚本用 jq。我现在处理 JSON 的固定习惯是三步收到接口返回先CtrlShiftV验证一次过了再CtrlShiftF格式化改完准备提交前再看一眼消息窗口有没有新错误。验证器就像一个廉价门禁把语法错误拦截在进入版本库之前。这个方法不能帮你发现业务逻辑错误但能省掉“提交后 CI 报 JSON 解析失败”的尴尬。希望帮到你。本文还有配套的精品资源点击获取