跳转至

第 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 中启用:

plugins=(git project)

这个插件遵循三条规则:依赖不存在就快速退出;函数使用局部变量;alias 和函数都在 README 中记录。

9.3 只提供补全的插件

如果只需要补全,可以使用:

custom/plugins/project/
└── _project

由于 is_plugin 会识别 _$name,它会把目录加入 fpath。如果没有 project.plugin.zsh,后续 _omz_source 找不到主动入口也不会报错;按 Tab 时 zsh 会按约定 autoload _project

9.4 覆盖内置插件要不要复制?

通常不要复制完整文件。更稳的做法是:

# custom/after-omz.zsh
unalias gr 2>/dev/null
alias gr='git restore'

只有需要替换内置入口、并且你愿意长期维护一个 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_infogit_prompt_status 等公共函数;
  • 使用 precmd/preexec 做动态刷新;
  • 提供明确的配置变量。

不要在主题文件里重复实现 Git dirty 检测;不要每次 prompt 都启动多个 git statussedawk 子进程;不要把未经转义的 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,越可以按自己的环境取舍。