yazi — Rust 写的异步 TUI 文件管理器,终端里直接看图
是什么
yazi 是 sxyazi 在 2023 年用 Rust 写的命令行文件管理器。它在终端里铺出三栏画面(左父目录、中当前、右预览),光标用 vim 的 hjkl 走,但底层做了两件 ranger 没做的事:全异步 I/O 和 原生图片协议。
日常类比:
把
ranger拆掉旧引擎,换上电动机——同一辆车的方向盘、踏板、座椅都没变,但起步、转向、爬坡的体感全部不一样。
最直观的画面,光标停在中间栏的某张 PNG:
~/photos 2024/ IMG_0312.png 2024/ > raw/ > ┌─────────────┐ raw/ export/ │ (实际图片) │ export/ README.md │ │ └─────────────┘普通 ranger 在这一格通常是 ASCII 字符画或一行 image: 1920x1080;yazi 直接通过 kitty graphics protocol 把 PNG 像素送进终端缓冲区——终端里就是真图。
为什么重要
不了解 yazi,下面这些场景每天都要付学费:
- 在 10 万文件的目录里
ls,ranger 同步遍历卡 3-5 秒不能动;yazi 异步在 100ms 内先出首屏,剩下的边滑边补 - 服务器 ssh 上去想看一眼某张截图——ranger 要装 w3mimgdisplay + 改 scope.sh + 配终端,yazi 默认就有
- 想给文件管理器加自定义预览(比如
.parquet用duckdb抽前 10 行),ranger 改 shell 脚本,yazi 写一段 Lua - 给同事推荐 ranger,对方装完发现”预览图片是 ASCII 字符画”——劝退
yazi 和 fzf / fd / bat 是同一个潮流:把 90 年代的 Python/C 终端工具用 Rust + 现代终端协议重写一遍。同代竞品 lf / nnn 没做图片协议,ranger 做了但要外挂;yazi 是这条赛道目前的 SOTA。
核心要点
yazi 的设计可以拆成 三个支柱:
-
异步运行时:基于 tokio 的 task scheduler,把列目录、读 git 状态、生成预览全部丢进后台 task。光标移动 → UI 立刻刷新,预览在 100ms 后追上。类比:餐厅把”点单”和”出菜”分开两条线,前台不会因为后厨忙就停下来。
-
原生图片协议:检测终端能力后选 kitty graphics / iTerm2 inline / Sixel / chafa(ASCII fallback)四级降级。终端是 iTerm2 / Kitty / WezTerm 时直接送像素;普通终端退到字符画。类比:网页根据浏览器能力切 WebP / JPG / GIF,能力探测后再发。
-
Lua 插件 + DDS 消息总线:UI、键位、预览器都用 Lua 描述;
ya命令行通过 DDS(Data Distribution Service)给运行中的 yazi 发消息。类比:游戏的 mod 系统——主程序留口子,玩家用脚本语言写扩展,运行时挂上去不重启。
三件事叠加,结果是”用起来像 ranger,但快一个量级、看图不折腾、改起来不用碰 Rust”。
实践案例
案例 1:装上、跑起来、第一次看图
brew install yazi # macOS# 或 cargo install --locked yazi-fm yazi-cliyazi进入后用 hjkl 走、/ 搜索、q 退出。把光标停在任意 PNG / JPG 上:
- iTerm2 / Kitty / WezTerm:右栏直接出真图
- Terminal.app(macOS 自带):右栏是 chafa 字符画 fallback
第一次跑就能感觉到和 ranger 的差别——不用配 scope.sh、不用装 w3mimgdisplay、不用编辑配置文件。
案例 2:用 Lua 加一个 parquet 预览器
ranger 加预览要改 shell 脚本,yazi 写 Lua 文件丢进 ~/.config/yazi/plugins/parquet.yazi/init.lua:
local M = {}
function M:peek() local cmd = Command("duckdb") :args({ "-c", "SELECT * FROM '" .. tostring(self.file.url) .. "' LIMIT 10" }) :stdout(Command.PIPED) :output()
if cmd then ya.preview_widgets(self, { ui.Text(cmd.stdout):area(self.area), }) endend
return M再在 yazi.toml 里写一行 parquet = "parquet",下次光标停在 .parquet 文件上就能看见前 10 行。写过一次永远都在。
案例 3:用 ya pub 让外部脚本和 yazi 对话
DDS(消息总线)让你从外面给 yazi 发命令。比如想”在外部脚本跑完后让 yazi 跳到生成的目录”:
some-build-script.sh && ya pub --str "$(pwd)" cdyazi 收到 cd 消息后跳过去。这个机制让 yazi 能嵌进任何已有的 shell 工作流,而不只是”被人开来用”。
踩过的坑
-
图片预览看终端脸色:iTerm2 / Kitty / WezTerm / Ghostty 原生支持,Terminal.app / gnome-terminal / VS Code 内置终端只能退到 chafa ASCII。换终端比改配置便宜。
-
0.x API 不稳:Lua 插件 API 在 0.x 期间会破坏性变更。升级前看 CHANGELOG,第三方插件经常要等几天才跟上。
-
从 ranger 迁移的肌肉记忆冲突:ranger 默认”光标即选中”,yazi 默认”光标只是 cursor,space 才 toggle 选择”。前两天会反复”以为选了实际没选”。这是设计选择不是 bug,避免误删。
-
Windows 边缘 case:路径大小写、长路径、权限有偶发问题。主战场是 macOS / Linux。
-
ya 不是 yazi:
ya是 CLI 配套(发消息、装插件),yazi是 TUI 主程序。装的时候两个都要。
适用 vs 不适用场景
适用:
- iTerm2 / Kitty / WezTerm 用户想在终端里直接看图、看 PDF 缩略图
- 大目录(10 万 + 文件)下 ranger 卡到不能用
- 想给文件管理器加自定义预览但不想写 shell
- ranger 用户嫌”装一堆外挂才能看图”
不适用:
- 终端是 Terminal.app / gnome-terminal —— 图片协议退化,体验和 ranger 差不多
- 已经有大量 ranger 配置(rifle.conf / scope.sh / commands.py)—— 迁移成本高
- 需要稳定脚本化 API —— 0.x 还在改
- 偏好极简(nnn / lf 风格)—— yazi 默认功能比它们多
学到什么
- 异步 I/O 在 TUI 里同样有意义——不只是 web server 才需要 tokio
- 能力探测 + 多级降级 是兼容老终端的工业做法(图片协议四级 fallback)
- Lua 插件比 shell 脚本更适合扩展 TUI——不用 fork 进程、能传结构化数据
- DDS 消息总线 让 TUI 程序能嵌进 shell 工作流,而不只是”被人开来用”
延伸阅读
- 官方文档:yazi-rs.github.io (配置、插件、键位映射全在这里)
- 插件市场:yazi-rs/plugins (社区贡献的预览器、主题、键位包)
- 终端图片协议综述:Kitty graphics protocol spec (理解 yazi 怎么把像素送进终端)