第 9 章:扩展开发 —— 写插件、主题和 custom 覆盖¶
9.1 先决定你要扩展哪一层¶
| 需求 | 合适位置 |
|---|---|
| 个人 alias/函数 | $ZSH_CUSTOM/*.zsh |
| 一组可启用功能 | $ZSH_CUSTOM/plugins/<name>/ |
| prompt 外观 | $ZSH_CUSTOM/themes/<name>.zsh-theme |
| 所有人默认的基础行为 | fork/贡献上游 lib/ |
| 依赖外部 CLI 的补全 | plugin completions/ 或 cache 生成 |
不要把个人配置直接改进 $ZSH/lib 或 $ZSH/themes:Git 更新会覆盖本地修改,也无法清楚区分哪些行为来自你自己。
9.2 最小自定义插件¶
# $ZSH_CUSTOM/plugins/project/project.plugin.zsh
(( $+commands[rg] )) || return
function croot() {
local root
root=$(git rev-parse --show-toplevel 2>/dev/null) || return
cd "$root"
}
alias rgi='rg --hidden --glob "!.git"'
在 .zshrc 中启用:
这个插件遵循三条规则:依赖不存在就快速退出;函数使用局部变量;alias 和函数都在 README 中记录。
9.3 只提供补全的插件¶
如果只需要补全,可以使用:
由于 is_plugin 会识别 _$name,它会把目录加入 fpath。如果没有 project.plugin.zsh,后续 _omz_source 找不到主动入口也不会报错;按 Tab 时 zsh 会按约定 autoload _project。
9.4 覆盖内置插件要不要复制?¶
通常不要复制完整文件。更稳的做法是:
只有需要替换内置入口、并且你愿意长期维护一个 fork 时,才创建同名 custom plugin。替换时应在文件顶部注明:
- 被替换的上游文件;
- 复制时的上游版本/日期;
- 你保留了哪些行为;
- 未来升级需要重新 diff 的位置。
9.5 配置要放在 source 前还是后?¶
source 前:影响初始化过程
- zstyle alias 策略
- update mode/frequency
- completion 安全开关
- DISABLE_* 和主题输入变量
source 后:覆盖最终结果
- 最终 alias
- 用户函数
- PROMPT/RPROMPT 微调
- 只影响交互时的本地快捷方式
如果 unsure,检查目标变量在哪里被读取。源码在 source 前读取的变量,source 后再设置通常已经太晚。
9.6 自定义主题的约束¶
主题应尽量只负责:
- 定义
PROMPT/RPROMPT; - 调用
git_prompt_info、git_prompt_status等公共函数; - 使用
precmd/preexec做动态刷新; - 提供明确的配置变量。
不要在主题文件里重复实现 Git dirty 检测;不要每次 prompt 都启动多个 git status、sed、awk 子进程;不要把未经转义的 branch 或路径直接拼到 prompt expansion 中。
9.7 测试矩阵¶
扩展至少在下列场景验证:
| 场景 | 需要观察 |
|---|---|
干净 shell:zsh -f |
依赖是否正确声明 |
| 无外部命令 | 是否安全 return,不刷错误 |
| 非 Git 目录 | prompt 是否隐藏 Git 片段 |
| dirty Git 目录 | 未跟踪/已修改状态是否正确 |
| SSH/Emacs/vterm | terminal hook 是否误输出控制序列 |
| macOS/Linux/WSL | ls、剪贴板、路径和默认命令差异 |
| 重复 source | 是否重复注册 hook、alias 或异步 handler |
基本检查命令:
zsh -n "$ZSH_CUSTOM/plugins/project/project.plugin.zsh"
zsh -lic 'source "$ZSH/oh-my-zsh.sh"; whence -v croot'
9.8 贡献上游时的最小 PR 证据¶
一个高质量插件/主题改动应包含:
- README 里的使用方式和新增 alias/function;
- 外部依赖不存在时的行为;
zsh -n结果;- 启动耗时是否变化;
- Linux/macOS 等平台差异;
- 是否需要更新 wiki 截图或主题列表。
Oh My Zsh 的扩展面很宽,但它的契约很薄。越靠近 core,越要少做假设、少产生副作用;越靠近 custom,越可以按自己的环境取舍。