跳转到内容

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 默认就有
  • 想给文件管理器加自定义预览(比如 .parquetduckdb 抽前 10 行),ranger 改 shell 脚本,yazi 写一段 Lua
  • 给同事推荐 ranger,对方装完发现”预览图片是 ASCII 字符画”——劝退

yazi 和 fzf / fd / bat 是同一个潮流:把 90 年代的 Python/C 终端工具用 Rust + 现代终端协议重写一遍。同代竞品 lf / nnn 没做图片协议,ranger 做了但要外挂;yazi 是这条赛道目前的 SOTA。

核心要点

yazi 的设计可以拆成 三个支柱

  1. 异步运行时:基于 tokio 的 task scheduler,把列目录、读 git 状态、生成预览全部丢进后台 task。光标移动 → UI 立刻刷新,预览在 100ms 后追上。类比:餐厅把”点单”和”出菜”分开两条线,前台不会因为后厨忙就停下来。

  2. 原生图片协议:检测终端能力后选 kitty graphics / iTerm2 inline / Sixel / chafa(ASCII fallback)四级降级。终端是 iTerm2 / Kitty / WezTerm 时直接送像素;普通终端退到字符画。类比:网页根据浏览器能力切 WebP / JPG / GIF,能力探测后再发

  3. Lua 插件 + DDS 消息总线:UI、键位、预览器都用 Lua 描述;ya 命令行通过 DDS(Data Distribution Service)给运行中的 yazi 发消息。类比:游戏的 mod 系统——主程序留口子,玩家用脚本语言写扩展,运行时挂上去不重启。

三件事叠加,结果是”用起来像 ranger,但快一个量级、看图不折腾、改起来不用碰 Rust”。

实践案例

案例 1:装上、跑起来、第一次看图

Terminal window
brew install yazi # macOS
# 或 cargo install --locked yazi-fm yazi-cli
yazi

进入后用 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),
})
end
end
return M

再在 yazi.toml 里写一行 parquet = "parquet",下次光标停在 .parquet 文件上就能看见前 10 行。写过一次永远都在

案例 3:用 ya pub 让外部脚本和 yazi 对话

DDS(消息总线)让你从外面给 yazi 发命令。比如想”在外部脚本跑完后让 yazi 跳到生成的目录”:

Terminal window
some-build-script.sh && ya pub --str "$(pwd)" cd

yazi 收到 cd 消息后跳过去。这个机制让 yazi 能嵌进任何已有的 shell 工作流,而不只是”被人开来用”。

踩过的坑

  1. 图片预览看终端脸色:iTerm2 / Kitty / WezTerm / Ghostty 原生支持,Terminal.app / gnome-terminal / VS Code 内置终端只能退到 chafa ASCII。换终端比改配置便宜。

  2. 0.x API 不稳:Lua 插件 API 在 0.x 期间会破坏性变更。升级前看 CHANGELOG,第三方插件经常要等几天才跟上。

  3. 从 ranger 迁移的肌肉记忆冲突:ranger 默认”光标即选中”,yazi 默认”光标只是 cursor,space 才 toggle 选择”。前两天会反复”以为选了实际没选”。这是设计选择不是 bug,避免误删。

  4. Windows 边缘 case:路径大小写、长路径、权限有偶发问题。主战场是 macOS / Linux。

  5. ya 不是 yaziya 是 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 默认功能比它们多

学到什么

  1. 异步 I/O 在 TUI 里同样有意义——不只是 web server 才需要 tokio
  2. 能力探测 + 多级降级 是兼容老终端的工业做法(图片协议四级 fallback)
  3. Lua 插件比 shell 脚本更适合扩展 TUI——不用 fork 进程、能传结构化数据
  4. DDS 消息总线 让 TUI 程序能嵌进 shell 工作流,而不只是”被人开来用”

延伸阅读

关联

  • ranger —— Python 三栏文件管理器,yazi 的视觉模型来源;yazi 把异步和原生图片补上
  • fzf —— 模糊查找器,yazi 的搜索面板有相似键位;可以和 yazi 互补
  • fd —— Rust 文件查找;yazi 内部也用 fd-like 逻辑列目录
  • bat —— Rust 高亮 cat;yazi 文本预览默认走 bat
  • broot —— Rust 交互式目录浏览器,赛道相邻但走不同路(broot 重过滤、yazi 重预览)
  • zellij —— Rust 终端复用器,常和 yazi 同屏使用