加速你的 zsh —— 最强 zsh 插件管理器 zplugin/zinit 教程
文章目录
【注】本文最后更新于 January 26, 2020,文中内容可能已经过时。
2020-01-26 更新:
Zplugin 已改名 Zinit,不过教程中的内容仍然适用。 阅读时请自行在脑中替换 (
2020-01-14 更新:
博主已经叛逃 powerlevel10k 了(注意是 10k 不是 9k)。 p10k 的 Instant Prompt 功能非常好用,它可以在其他插件的加载过程中提供一个精简的 prompt 供使用,相当于后台加载。
这个功能并不能加快实际的加载速度, 比如 time zsh -ic 'exit'
的结果仍然不会变,但是使用体验提升巨大(体验上几乎是瞬间加载)!
读到这里的读者建议先尝试一下 p10k(如果很满意就不用往下看了)。
我已经去掉了配置文件中所有的 zinit 延迟加载语句,不过 zinit 即使去掉延迟加载功能仍然十分强大,我还是选择继续使用 zinit。 而且如果加载了特别耗时的插件的话,zinit 仍然是有用的。
前言
Zinit 是个冷门但是却强大无比的 zsh 插件管理器,它拥有一个 killer feature —— Turbo mode,可以让插件在后台加载。这意味这你可以先加载最重要的插件,比如语法高亮和自动建议,剩下的可以统统放到后台加载,让你的 zsh 尽快进入可用状态。
利用这个机制,zinit 可以将 zsh 的启动时间缩短到几十毫秒——以我的配置为例,只需要 35 毫秒左右。而使用传统的插件管理器比如 antigen,需要近 200 毫秒才能加载完成。
这里有一张图,对比了不同插件管理器的速度(来源:Comparison of ZSH frameworks and plugin managers)
可以看到 zinit 在插件数目变多时速度……似乎更快了??? 什么鬼,这个大概是实验误差吧,也有可能是第一次启动时编译了自身所以后面变快了。不过 zinit 的速度是毋庸置疑的,哪怕这个插件需要数十秒来加载,只要放在后台加载,一样不影响你的 zsh 启动。
然而!!这个工具虽然强大,却没多少名气,让人倍感惋惜。
所以写下这篇文章,希望能有更多人了解到这个工具。 不过本文只涉及了它强大功能的冰山一角,深入研究推荐阅读 Zinit Wiki 和 Zinit 的 README。
(虽然说是冰山一角,本文的内容也足以覆盖绝大多数情况了)
(如果你很懒不想研究的话,文末还有一份完整的示例配置,安装完成后可以直接使用)
安装
自动安装
官方推荐的安装方式,一键完成。不过让我很没有安全感,我倾向于手动安装。
|
|
手动安装
首先 clone repo 到随便哪个位置
|
|
然后在你的 ~/.zshrc 顶端添加如下语句
|
|
安装完成,非常简单。
配置
我本打算从头写一篇教程,但是感觉自己要么讲得太多了,要么又讲得太少了。干脆直接翻译一下文档的 Introduction 部分吧(
在本篇文档中,你将学会如何:
- 在 Zplugin 中使用 Oh My Zsh 和 Prezto 框架
- 管理补全
- 使用 Turbo mode
- 使用 ice 修饰词比如
as"program"
等等
基本插件加载
|
|
以上命令展示了两种最基本的加载插件的方式。
load
会启用分析功能——你可以通过zinit report {plugin-spec}
跟踪插件具体做了什么,也可以使用zinit unload {plugin-spec}
卸载插件。light
不会跟踪加载过程,可以提升加载速度,但是会导致失去查看插件报告和动态卸载插件的能力。
开启 Turbo mode 后跟踪插件所耗费的时间可以忽略不计
使用 Oh My Zsh & Prezto
为了加载 Oh My Zsh 和 Prezto 插件,可以使用 snippet
功能加载代码片段。代码片段是指通过 curl
、wget
等工具下载的单个文件。后面直接跟 URL 即可(会自动判断下载工具)。举例:
|
|
此外,对于 Oh My Zsh 和 Prezto,你还可以使用缩写 OMZ::
和 PZT::
:
|
|
此外的此外,snippet 还支持 Subversion 协议,which GitHub 也支持。这可以让你加载包含多个文件的代码片段(比如 Prezto module 就有可能包含两个或者更多的文件,像 init.zsh
和 alias.zsh
)。默认会被 source 的文件有:*.plugin.zsh
, init.zsh
, *.zsh-theme
:
|
|
代码片段和性能
通过 curl
,wget
等工具和 SVN ,你可以几乎完全避免加载 Oh My Zsh 和 Prezto 或者是其他框架的代码。这可以提高 Zplugin
的性能,而且更快更紧凑(指占用内存更小并且加载时间更短)。
一些 Ice 修饰词
命令 zinit ice
为下一条命令提供了 Ice 修饰词(详见 README ice-modifiers 一节)。啥意思呢:“ice” 是指一些被添加物(就像被添加到饮料或者咖啡里面的冰块)——在 Zplugin 中这意味着 ice 是被添加到下一条命令中的修饰词,冰块会融化(所以不会持续起作用)——在 Zplugin 中这意味着修饰词只对下一条命令起效。举例来说使用 pick
ice 可以显示地选择被执行的文件:(译注:绕半天,其实就是一种实现可选参数的方法)
|
|
ice 修饰词的内容可以简单地放在 "..."
, '...'
或 $'...'
中。不需要在 ice 修饰词名称的后面加上 ":"
(尽管你这么做也没问题,而且加 =
也是可以的。比如 pick="init.zsh"
或 pick=init.zsh
都是可行的)。
这样可以让 vim
,emacs
之类的编辑器和 zsh-users/zsh-syntax-highlighting
或 zdharma/fast-syntax-highlighting
能够高亮 ice 修饰词的内容。
as"program"
插件并不一定是需要被 source 的脚本,也可以是需要添加到 $PATH
中的命令。为了实现这种效果,需要以 program
为参数调用 as
ice (或者以 command
为参数也可以)
|
|
上面的代码会将插件目录添加到 $PATH
中,并复制文件 httpstat.sh
为 httpstat
,并为 pick
选中的文件(本例中为 httpstat
) 添加正确的可执行权限(+x
)。还有一个修饰词 mv
,它和 cp
的工作方式类似,只不过是移动文件而不是复制。mv
的优先级比cp
低。
cp
和mv
ice (还有其它的比如atclone
)只会在插件(或代码片段)被安装的时候运行。要想再次运行它们的话需要先使用zinit delete PZT::modules/osx
这类命令来删除插件)
atpull"…"
复制文件相比移动来说是个更佳选择,它便于进行后续更新——因为 repo 中的原始文件并不会被修改,所以 git
不会报告冲突。不过,要想使用 mv
也是可以的,只要你正确使用了 atpull
(一个在插件更新(update)的时候被调用的 ice):
|
|
atpull
后面的命令以感叹号开头,意味着它会在 git pull
和 mv
之前被执行。此外 atpull
, mv
, cp
都只会在获取到新的提交的时候被执行。
总而言之,当用户执行 zinit update b4b4r07/httpstat
来升级这个插件的时候,如果有新 commit,首先执行的是 git reset --hard
——它会恢复原来的 httpstat.sh
,然后 git pull
被执行并拉取新的 commit(进行快进),然后 mv
再次被执行将命令名称修改为 httpstat
而不是 httpstat.sh
。这样 mv
可以用于永久性更新插件的内容而且不会阻碍插件使用 git
(或 subversion
)更新。
在 zsh 的交互式会话中,为了避免感叹号被展开,请使用
'...'
而不是"..."
来包裹atpull
ice 的内容
通过 snippet 安装命令
也可以使用 snippet 添加命令。比如:
|
|
注:Snippet 也支持 atpull
,所以可以这样写 atpull'!svn revert'
。还有 atinit
,可以在每次加载插件或 snippet 的时候被执行。
通过 snippet 安装补全
以 completion
为参数调用 as
ice,可以让 snippet
命令直接加载一个补全文件,比如:
|
|
补全管理
Zplugin 允许禁用/启动任意插件的任意一条补全。试着安装一个提供了补全的流行插件:
|
|
第一条命令(blockf
ice)将会阻断传统的添加补全的方式。zinit 会使用它自己的方式(基于符号链接而不是往 $fpath
里加一堆目录)。Zplugin 将会自动安装它下载的插件的补全。想要卸载这些补全并且重新安装的话,可以使用:
|
|
列出补全
注:
zi
是一个可以在交互式会话中使用的别名
要以表格形式查看每个插件都提供哪些补全和插件的名字,请使用
|
|
这个命令特别适用于 zsh-users/zsh-completions
这类提供了大量补全的插件——表格每行将会展示三个补全(这样可以占用的终端页面的大小)就像这样
|
|
你也可以通过给 clist
添加参数来提高每行显示的补全的数目,比如 zi clist 6
将会显示:
|
|
启用和禁用补全
补全可以被禁用,这样就可以调用 zsh 的原始补全。这个命令非常简单,它只需要补全的名称作为参数
|
|
就这么简单。还有一个命令 zinit csearch
,可以搜索所有的插件目录列出所有可用的补全并且展示它们是否被启用。
这就实现了对补全的完全控制。
子目录的 SVN 支持
通常,为了使用 GitHub 项目的子目录作为 snippet,需要在 URL中添加 /trunk/{path-to-dir}
,比如
|
|
snippet 也会默认自动安全可用补全,就像 plugin 一样。
Turbo Mode (Zsh >= 5.3)
wait
ice 允许你将插件的加载过程延迟到 .zshrc
加载完成并且 prompt 已经显示出来以后。就像 Windows 一样——在启动过程中,即使后台依然在加载数据,它也会显示桌面。尽管这有缺点,不过总比黑屏十分钟要好。
然而,在 Zplugin 中,这个方法没有缺点——窗口不会延迟、冻结等等——在插件被加载的过程中,你的命令行完全处于可用状态,即使插件数量有十多二十个。
Turbo mode 将会加速 zsh 的启动过程 50%~73% 之多。比如原先是 200ms,现在就只需要 50ms!
这个功能需要 Zsh 5.3 及以上版本。为了使用 Turbo mode,可以参照以下方式为你的插件添加 wait
ice:
|
|
上面的代码让 psprint/zprompts
插件在 zshrc
处理完毕后的 0
秒后启动。实际它会在基本的命令提示符 READY >
出现后的大概 1ms 后启动。我已经使用这种方式来设置我的命令提示符两年多了,没有丝毫问题。 只提供 wait
一个词也是可以的,它的效果等同于 wait'0'
(同样 wait'!'
等同于 wait'!0'
)
感叹号让 Zplugin 在插件加载完毕后重设命令提示符,对于延迟加载主题来说是很有必要的。Prezto 主题也是一样,下面的例子使用了更长的延迟
|
|
延迟加载 zsh-users/zsh-autosuggestions
|
|
解释:Autosuggesstions 使用了 precmd
钩子,它会在处理完 zshrc
之后(刚好在第一个命令提示符出现之前)被调用。然而 Turbo mode 会在 zshrc
加载完成 1s 后再加载它,使得在第一个命令提示符下 precmd
将不会被安装并调用。这就会让 autosuggesstions 在第一个命令提示符下处于不可用状态。但 atload
ice 可以修复这个问题,它可以在插件加载完成后调用同样的函数,就像 precmd
做的那样,这样就可以获得一致的体验。
lucid
ice 可以隐藏 Turbo mode 下插件加载完成的提示,类似 Loaded zsh-users/zsh-autosuggestions
Turbo Mode 加载复杂的命令提示符
某些高级主题的初始化过程是通过 precmd
钩子完成的(一些需要在每个命令提示符出现之前被调用的函数)。这个钩子被通过 zsh 函数 add-zsh-hook 以将函数名添加到 $precmd_functions
数组中的方式被安装。
为了使命令提示符在用 Turbo mode 半路加载完主题后被完全初始化,需要使用 atload''
ice 调用这个 hook。
首先,检查 $precmd_function
数字来获取钩子函数的名称。举例来说,在 robobenklein/zinc
主题中,将会有两个函数:prompt_zinc_setup
和 prompt_zinc_precmd
:
|
|
然后,把他们添加到 atload''
ice 中
|
|
atload'!...'
中的感叹号会让 Zplugin 跟踪这个函数以便卸载插件,详见 这儿。这个对于接下来会提到的设置多个命令提示符会有用。
按条件自动加载/卸载
load
和 unload
ice 允许你定义插件什么时候需要被激活或者禁用。举例:
|
|
两个命令提示符,每个都在不同的目录下生效,这个技术可以用来定义不同的插件组,比如定义一个 $PLUGINS
和可能的值比如 cpp
, web
, admin
,并且设置 load
/unload
条件来激活 cpp
, web
中不同的插件。
load
/unload
和wait
的不同之处是它始处于激活状态,而不是只在只在第一次加载时有效。
需要注意的是,要使卸载插件功能正常工作,你需要跟踪插件的加载过程(所以需要使用 zinit load ...
而不是 zinit light ...
)。跟踪过程有轻微的性能损耗,不能在开启了 Turbo mode 后并不后影响 zsh 的启动时间。
可以参见 WIki 的 multi prompts 一节,它包含了一个使用多个命令提示符的跟现实的例子,和作者自己目前所使用的类似。
常见问题
如何升级
使用 zinit self-update
可以升级 zinit
使用 zinit update
可以升级所有插件,也可以通过 zinit update {插件名称}
来升级单个插件。
如何使用 OMZ 主题
首先,OMZ 主题基本上都使用了 OMZ 提供的 git 库,因此使用这些主题之前需要先加载 git.zsh
|
|
否则可能会收到这样的错误
|
|
部分主题可能还需要加载 OMZ::lib/theme-and-appearance.zsh
为什么我的键位绑定失效了
因为以前 OMZ 替你做了绑定,不用 OMZ 当然就没有了。不过只要一行代码就能重新启用:
|
|
为什么我的补全失效了
因为以往也是 OMZ 替你做这件事情的……有两种解决办法
-
如果你没有延迟加载任何补全相关的插件的话,直接在配置结尾加上下面的代码即可
1 2 3 4
# 初始化补全 autoload -Uz compinit; compinit # zinit 出于效率考虑会截获 compdef 调用,放到最后再统一应用,可以节省不少时间 zinit cdreplay -q
-
如果你延迟加载了和补全有关的插件(但凡提供了补全就算),就比较复杂。一般推荐的做法是给最后一个被加载的补全相关的插件加上
atload='zpcompinit; zpcdreplay'
的修饰词。如:1 2 3
# 假设 git 插件是最后加载的 zinit ice lucid wait="0" atload="zpcompinit; zpcdreplay" zinit snippet OMZ::plugins/git/git.plugin.zsh
Zplugin Module
这也是一个强力功能,不过在 Turbo Mode 的光环下就黯然失色了。
它的功能是自动编译被 source 的脚本,可以进一步提升启动速度。不过要注意这个过程中没有 alias 展开,因此不要在脚本中使用 alias。
而且它还有一个强大的功能——查看每个被 source 过的脚本的执行时间,由此可以迅速找出拖慢你 zsh 启动速度的元凶。
安装方式
未安装 Zplugin
没有安装 Zplugin 时也是可以使用这个模块的,对于某些不带编译功能的插件管理器来说有一定帮助。
|
|
脚本执行完会提示你添加两行代码到 ~/.zshrc
顶部
已安装 Zplugin
|
|
同样,脚本执行完会提示你添加两行代码到 ~/.zshrc
顶部
用法
使用 zpmod source-study
就可以查看每个脚本的执行时间了,加上 -l
参数还可以显示脚本的完整路径。十分好用!
关于 Oh My Zsh 碎碎念
有不少 zsh 用户都嫌弃 Oh My Zsh(以下简称 OMZ),主要嫌弃它的速度太慢。(还有很蛋疼的一点就是想找点 zsh 语法的教程看看,结果搜索结果清一色的 OMZ 配置教程,只有一篇文章是在讲 zsh 本身的。行吧,我去 RTFM 了。)
这个慢主要体现在三点:
1. 粘贴代码太慢
这个确实没得洗,OMZ 默认启用了一个非常烦人的自动 URL 转义的功能。这个功能会极大拖慢粘贴的速度,而且这个功能在长达数年的时间里都是无法被除了修改源码以外的方式关闭的(oh-my-zsh#5569),直到今年五月份才终于加入了一个用于控制这个功能开关的变量,然而似乎还是默认开启的。这个功能不知道劝退了多少 OMZ 的用户……
总之,如果你是 OMZ 的忠实粉丝,看完本文后依然坚持使用 OMZ,建议立即更新并使用 DISABLE_MAGIC_FUNCTIONS=true
关闭这个功能。
2. 命令提示符响应太慢
这个其实是可以洗的——你别选那么花哨的主题就行了啊。
拖慢命令提示符响应速度的大头是 git 信息的统计。这个功能其实挺不错的,然而会导致在首次进入大 repo 时卡顿,尤其是在 HDD 上,甚至能卡数秒,难以忍受。
那么 OMZ 有没有不显示git 信息的主题呢?
答案是没有……
那么简单的主题你好意思提交到 OMZ 上去吗!这样的主题只能自己写了,比如我用了挺久的 loli 主题 (然而没有任何 loli 要素)。
不过看完这篇文章你肯定会想,既然插件可以异步加载,git 信息可不可以异步加载呢?
答案是可以!一个著名主题 Pure 就是这么做的,兼顾了美观与实用性,即使再大的 repo 也能够秒进。
3. 启动速度太慢
这个和 OMZ 没啥关系,主要是你插件太多了。除了 Zinit 外,其他插件管理器/框架都存在这个问题。
解决方案就是换 Zinit(其实还有一个实现了多线程加载的 Zplug,就是文章开头那张图中最慢的那一个……Zplug 的想法很好,但实现很挫)
Zinit 很强,但是我不想抛弃 OMZ 怎么办
前面已经说了,Zinit 可以直接加载 OMZ 的插件,所以 OMZ 的好处你都能享受到。
当然,你会发现即使加载了 OMZ 插件,使用体验还是和 OMZ 不一致。 因为 zsh 的很多功能是默认没有开启的,你可以手动开启,或者如果你和我一样懒的话,还可以直接用 snippet 选择性加载 OMZ 的部分功能。举例如下:
|
|
上面的代码加载了 OMZ 对补全、历史、键位绑定、主题等的设置,对于我来说基本上和 OMZ 的使用体验就一致了。如果还想要什么功能的话,可以直接去 OMZ 的 lib 目录下找。
一份示例配置
|
|
安装完 Zinit 后,可以将以上代码粘贴到 ~/.zshrc
中,然后建议挂着代理启用 zsh(因为会从 GitHub clone,所以第一次启动会比较慢,根据你的网络状况可能需要几十秒到几十分钟不等
本来还有一段安装一些常用工具的代码,不过一方面这些工具最好的安装方式应该是通过系统包管理工具安装,另一方面加上这段可能会让网络不好的朋友非常痛苦,故单独提出来放在下面。
需要的话可以把这段放到 source 后面
|
|