【免费下载链接】disktree
A treemap for finding and removing what fills your disk, for Omarchy. Rust + GPUI.
这篇技术指南围绕 disktree 仓库根目录下的 AGENTS.md 展开。AGENTS.md 不是面向终端用户的产品手册(那是 README.md 的职责),而是面向 Agent 与协作开发者的"工作契约":它定义了产品两阶段交互模型、一整套make/cargo xtask构建与质量门禁、严格的 lint 风格约定、九条承载正确性的架构不变量(invariants),以及一张"改动应该落在哪个文件"的职责映射表。读完本文,你将理解 disktree 的扫描、聚合、树图布局与删除保护分别由哪些源码模块保证,为什么某些看似别扭的实现(+1哨兵计数、派生而非跟踪的own_bytes、绝对面包屑)是正确性所必需的,以及如何在提交代码前通过 lint 门禁。
一、产品定位:一个磁盘占用探索器的两阶段工作模型
AGENTS.md 开篇即说明项目本质:一个面向 Omarchy 的 GPUI + gpui-omarchy 磁盘树图(treemap)浏览器,用于找出磁盘上被什么占满、标记待删除路径、复核清单并执行删除——整个过程中卷的剩余空间实时显示在界面上。
交互被划分为两个阶段:
- 探索(explore)阶段:树图(treemap)、面包屑(breadcrumbs)、选择行与状态行,用于浏览与定位。
- 复核(review)阶段:已标记清单、删除模式选择、二次确认,用于执行清理。
其中有一条贯穿始终的产品原则被 AGENTS.md 反复强调:"Marking is never destructive"(标记本身绝不具有破坏性)。标记只是把绝对路径记进清单并测算可回收空间,任何删除动作都必须走到复核阶段、经过确认才会真正发生。这一原则在 README.md 中体现为"nothing happens until you review the list and commit",也在 crates/disktree-core/src/removal.rs 的实现中体现为删除前的完整守卫(guard)检查。
二、构建与安装命令:从开发循环到发布产物的完整链路
AGENTS.md 给出的命令表是项目日常开发与交付的入口,与 Makefile 一一对应:
| 命令 | 作用 | Makefile 中的真实行为 |
|---|---|---|
make build | release 构建 | cargo build --release,产物为target/release/disktree |
make run | 构建并以$HOME为扫描根运行 | run: build后直接执行$(TARGET) |
make install | 安装到~/.local(二进制 + 桌面入口 + 图标) | 见下文 |
make install PREFIX=/usr/local | 系统级安装(需要 root) | 覆盖PREFIX变量后走同一 install 目标 |
make uninstall | 卸载 | 删除安装时写入的三样东西 |
make bundle | macOS 打包target/bundle/disktree.app及其 zip | cargo xtask bundle |
make lint | rustfmt 检查 + clippy 全告警提升为错误 | cargo xtask lint |
make test | 核心库与窗口测试 | cargo xtask test |
make ci | lint 后跑 test | ci: lint test |
make fmt | 原地格式化 | cargo xtask fmt-fix |
2.1make install:支持且唯一推荐的装机方式
AGENTS.md 明确"make installis the supported way",它安装三样东西到默认前缀~/.local(Makefile):
~/.local/bin/disktree——release 二进制;packaging/disktree.desktop.in渲染后的桌面入口——sed会把模板里的@BINDIR@与@VERSION@替换成真实安装前缀与 crate 版本(版本从 workspaceCargo.toml中version = "0.10.1"提取),这样 disktree 会出现在 Omarchy 启动器中,也会出现在文件管理器的目录"打开方式(Open with)"里(packaging/disktree.desktop.in 声明了MimeType=inode/directory;,它只添加处理器、绝不成为默认程序);assets/disktree.svg——图标,安装到share/icons/hicolor/scalable/apps。
安装脚本还会顺手执行update-desktop-database与desktop-file-validate(出错也不中断)。AGENTS.md 特别提醒:桌面入口的Categories必须保持单一主分类 + 附加分类(模板中为System;Filesystem;),否则desktop-file-validate会报错。安装完成后如果BINDIR不在 PATH 中,脚本会打印提示。
2.2 macOS 的差异化路径
在 Darwin 上,Makefile 的 install 目标被整体替换:先cargo xtask bundle生成 bundle,再ditto复制到~/Applications/disktree.app(APPS变量可改),并用符号链接在~/.local/bin/disktree暴露一个命令行入口,让 Spotlight 和终端都能找到它。AGENTS.md 特别强调两件事:
- bundle 由
packaging/macos/下的素材构建,即 Info.plist.in 模板与 AppIcon.svg 图标 SVG; - bundle identifier 是 macOS 所有权限授予的键,改动它会让用户重新授权,因此不要轻易修改。
此外 bundle 目标支持签名与公证:cargo xtask bundle --sign "Developer ID Application: Name (TEAMID)" --notarize(需要NOTARY_PROFILE等凭据),这在 xtask/src/main.rs 的 USAGE 中有完整说明。
2.3 Windows:无 make 环境下的等价门禁
Windows 没有 make,AGENTS.md 给出了直接等价命令:cargo xtask lint、cargo xtask test、cargo build --release。CI 在 Linux 与 Windows 两个系统上都会运行同样的门禁(gate),保证跨平台一致性。
三、代码规范:从 omatrack 继承的严格 lint 风格
AGENTS.md 的 "House rules" 部分规定了项目代码风格,它们全部可以在仓库配置中得到印证:
3.1 三级 lint 策略
workspace Cargo.toml 中设置了:
clippy::all与clippy::pedantic为deny(错误);clippy::nursery为warn;- 此外
rust_2018_idioms、unsafe_code为 deny,而cargo xtask lint再叠加-D warnings,把剩余警告一并升级为错误。
每个刻意豁免(allow)都必须写明理由,要么列在 workspaceCargo.toml的[workspace.lints.clippy]段,要么以#[allow(..., reason = "...")]写在条目上,绝不允许静默豁免。例如 tree.rs 中的#[allow(clippy::missing_const_for_fn, reason = "...")]。Cargo.toml中列出的豁免清单(module_name_repetitions、missing_errors_doc、cast_lossless等)都附有理由注释,例如struct_excessive_bools的豁免理由是"扫描选项本质就是布尔标志"。
3.2 80 列与 4 空格缩进
rustfmt.toml 设置max_width = 80、use_field_init_shorthand、use_try_shorthand,并注明只使用 stable rustfmt 选项——因为门禁运行在 stable 工具链上,nightly 专属选项(如wrap_comments)会被静默忽略而不是应用,这正是 AGENTS.md 所说的陷阱。
3.3 注释只写"为什么"
"Comments say why. The code says what." 任何不直观的数字、排序或边界都必须伴随理由。扫描代码中这类注释随处可见,例如 scan.rs 对TALLY_EVERY = 1024的解释(避免每个条目都让共享计数器产生缓存行抖动)、对WALK_POOL线程上限的实测记录(Windows 上 340 万文件的家目录 16 线程 6.5 秒、24 线程反而 8.1 秒)。
3.4 测试紧贴承诺
"Tests live beside the promise they make."disktree-core的测试针对真实临时目录树验证大小核算、布局与删除守卫;disktree-app/src/tests.rs驱动真实窗口(draw a frame, press keys),因此绘制时崩溃的界面会直接导致测试失败——这正是"窗口测试"相比纯逻辑测试更强的地方。
3.5 红线:绝不删除标记路径之外的任何东西
删除守卫位于 crates/disktree-core/src/removal.rs,AGENTS.md 称其为 load-bearing(承重)且有测试覆盖。removal.rs顶部注释点明三条优先级:绝不删除用户没有指向的东西、绝不进入另一个文件系统、永远能说清楚发生了什么。
四、九条架构不变量:正确性由模块边界保证
AGENTS.md 的 "Invariants" 部分是全文技术含量最高的章节,它定义了任何改动都不得破坏的九条规则。下面逐条结合源码佐证。
不变量 1:大小默认来自st_blocks × 512
磁盘占用(disk usage)默认按分配块计算,这是文件被删除后真正释放的字节数;ls -l展示的 apparent size 只是可切换的另一种度量。实现在 crates/disktree-core/src/size.rs(measure函数由 scan.rs 的Facts::of调用)。Windows 上则取自目录列表报告的分配量(见 windows.rs);以管理员权限扫描整个 NTFS 卷时改从主文件表(MFT)读取(mft.rs),它保持 walk 对隐藏项、链接、云文件夹与深度的规则,但统计每个流的分配量、包括备用数据流(ADS),因此对含备用流的文件,MFT 路径的数字可能超过 walk 的数字。
不变量 2:own_bytes/own_files是派生的,绝不手工跟踪
tree::aggregate从子节点计算总数;硬链接去重会把重复叶子的权重归零,任何直接改bytes的代码都会被覆盖。在 tree.rs 的Node结构上,own_bytes的文档明确写着 "Derived byaggregate",而 scan.rs 中PendingDir::build构造目录节点时把bytes/own_bytes故意留零,注释说明聚合必须由aggregate一次性从子节点导出。scan.rs的finish_tree在 walk 结束后调用aggregate_deduped(硬链接去重)或aggregate。
不变量 3:目录只有在自身扫描和所有子目录任务都完成后才构建
这就是PendingDir::pending中的+1哨兵。源码中每个PendingDir的pending: AtomicUsize初始为 1(代表目录自身),每派生一个子目录任务就fetch_add(1);signal_done 中fetch_sub(1)返回 1 时才构建该目录并向上冒泡。AGENTS.md 直言:"Building early silently drops whole subtrees — it has happened once",说明这是用事故换来的教训。README 也印证这种设计源于 dust 的做法。
不变量 4:只有扫描根之下的路径可被删除,且挂载点等被拒绝
removal.rs的refuse函数拒绝:挂载点、文件系统根、家目录、任何包含家目录的目录、符号链接目标;Windows 上还拒绝 profiles 目录(FOLDERID_UserProfiles,通常是C:\Users)下的每个直接子目录,但 profile 内部仍可删。README 的 "What it refuses to do" 给出了完整清单(/usr、/etc、/boot、/nix/store等系统树一律拒绝,即使权限允许)。删除还绝不经过 shell——一个叫-rf的文件就只是个文件。
不变量 5:标记以绝对路径为键,重扫后依然有效
标记不依赖树中的位置,因此重新扫描后标记不丢。crates/disktree-app/src/marks.rs 提供Marks实现;AGENTS.md 提到Marks::refresh会重读标记路径的最新大小、并丢弃已消失的条目。
不变量 6:树图是"绘制"出来的,不是组合出来的元素
成千上万个矩形属于单个 canvas 回调,标签也在其中成形以便裁剪到自己的 tile 内。这对应 crates/disktree-app/src/treemap_view.rs 的绘制实现与 crates/disktree-core/src/treemap.rs 的布局算法。treemap.rs顶部注释明确这是 Bruls、Huizing 与 van Wijk 的squarified(方块化)算法:持续生长一行 tile 直到最差纵横比不再改善,再在剩余空间中开始新行——这产生 KDirStat 风格的可读马赛克,而非朴素 slice-and-dice 的细条。
不变量 7:tile 面包屑是绝对的
treemap::layout接收被绘制节点的面包屑,每个 tile 扩展它,因此任意深度下都能从扫描根解析出该 tile。相对面包屑在~处看似正确,但下钻后会悄悄指向别的目录——对标记也一样致命。因此任何把路径转成面包屑的代码都必须从扫描根出发。treemap.rs 的TileKind::Node { crumbs }注释写着 "child indices from the scanned root",layout的文档说明root_crumbs是root在扫描树中的位置。
不变量 8:视图变换是缩放改变的唯一对象
布局在基准空间像素中运行并被缓存,screen = (base - origin) * scale。这意味着平移/缩放不触发重新布局,只有树、视口尺寸或请求深度变化才需要重排。treemap.rs 头注释:"Layout runs in pixels, in the coordinate space of an unzoomed viewport. The view applies its own pan and zoom when painting"。
不变量 9:状态栏只声称它测量得到的节省量
预测(projection)来自已标记字节数;最终数字来自删除前后statvfs的差值。也就是说界面上的"删除后可用空间"预告只是估算,删除完成后会用真实的文件系统统计替换。crates/disktree-core/src/space.rs 负责自由空间与预测逻辑。
五、改动归属:一张决定"改哪里"的职责映射表
AGENTS.md 提供的映射表是协作开发的核心导航工具,它把每个改动需求指向唯一模块:
| 改动类型 | 归属文件 |
|---|---|
| 测量、过滤、并行 | crates/disktree-core/src/scan.rs |
| 节点定义或派生总量 | crates/disktree-core/src/tree.rs |
| tile 几何、嵌套、合并尾部 | crates/disktree-core/src/treemap.rs |
| 任何删除或拒绝删除的逻辑 | crates/disktree-core/src/removal.rs |
| 自由空间与预测 | crates/disktree-core/src/space.rs |
| Windows 上列表、测量、比较的不同 | crates/disktree-core/src/windows.rs(唯一的unsafe) |
| 从文件表读取整个 NTFS 卷 | crates/disktree-core/src/mft.rs |
| 按键、界面转换、标记 | crates/disktree-app/src/state.rs |
| 间距、字体与字号 | crates/disktree-app/src/ui.rs(只有 token,布局里没有px) |
| 马赛克的绘制或标签 | crates/disktree-app/src/treemap_view.rs |
| 某个屏幕的布局 | crates/disktree-app/src/views.rs |
| 从主题派生的颜色 | crates/disktree-app/src/palette.rs |
这张表的底层是 workspace 的 crate 划分:Cargo.toml 声明成员crates/disktree-app、crates/disktree-core、xtask;lib.rs 的开头注释说明了拆分的动机——必须正确的部分(大小核算、硬链接去重、纵横比布局、可删除性判定)可以在没有 GPUI、没有显示器、没有 GPU 的环境下独立构建与测试。这正是"无 UI 依赖"的核心库设计哲学。
几个值得注意的实现细节:
ui.rs规定"tokens only, nopxin layout",配合 README.md 提到的 GPUI Kit 设计指南:所有尺寸落在一个rem刻度上,界面缩放保持比例,primary 色保留给 Enter 的默认动作。windows.rs是全项目唯一允许unsafe的文件(workspaceCargo.toml将unsafe_code设为 deny,lib.rs 中mod windows;是#[cfg(windows)]条件编译的)。treemap.rs中LayoutOptions的默认值(max_depth: 3、min_tile: 5.0、max_children: 96、padding: 1.0、padding_outer: 3.0、header: 20.0、header_inner: 15.0)体现了"第一层结构要先于内部细节被读出来"的设计意图——顶层目录间距padding_outer比普通padding更宽。长子列表的尾部合并进一个Otherstile,保证面积被计入而非静默丢弃。
六、验证期望:测试体系覆盖的承诺面
AGENTS.md 的 "Verification expectations" 给出三层验证承诺:
- 大小核算(硬链接、符号链接、隐藏条目、深度限制)、删除守卫与squarified 布局,由
disktree-core对真实临时目录树的测试覆盖; - 界面由窗口测试覆盖——绘制帧、按键,包括一个端到端用例:标记目录 → 确认删除 → 断言文件消失而未被标记的邻居不受影响(README 也提到这个用例);
- 渲染除测试外还在真实家目录上运行验证过,但没有在每个主题与每种字号下人工检查过——这句诚实的边界声明本身就是契约的一部分。
七、对开发者的实践建议
把 AGENTS.md 当作一份"改动前必读"清单来用:
- 提交前跑门禁:
make lint(或 Windows 上cargo xtask lint)必须绿,且它不会帮你修复任何东西——本地红了,CI 也会红,请修复后再继续。 - 按映射表找文件:想改测量就进
scan.rs,想改删除就进removal.rs,不要跨模块硬改,尤其不要碰own_bytes这类派生字段。 - 尊重九条不变量:新增功能时逐条核对——例如新增任何删除路径都要先过
refuse/linked守卫;新增任何位置相关逻辑都要使用从扫描根出发的绝对面包屑。 - 测试写在承诺旁边:给新的删除守卫写真实临时目录上的测试;给新的界面行为写窗口测试,确保绘制不崩溃。
- 豁免必须带理由:如果实在需要
#[allow],写明reason,并在Cargo.toml或代码注释里留下可追溯的解释。
AGENTS.md 的全部内容——命令、规范、九条不变量、职责映射与验证期望——都在仓库源码中有一一对应的实现证据。把它与 README.md 搭配阅读,即可同时获得"产品怎么用"与"代码为什么这么写"两个视角的完整图景。
【免费下载链接】disktree
A treemap for finding and removing what fills your disk, for Omarchy. Rust + GPUI.
相关推荐
5分钟跑通BlockSuite预设编辑器:三步挂出生产级编辑器
5分钟跑通BlockSuite预设编辑器:三步挂出生产级编辑器 从零搭一个协作编辑器像自己盖房子:文本编辑、选区、粘贴、协同同步全得自己造轮子。BlockSui
前端UI组件富文本gogcli 架构与契约解析:Google Workspace 终端 CLI 的命令树、认证存储与开发门禁体系
gogcli 架构与契约解析:Google Workspace 终端 CLI 的命令树、认证存储与开发门禁体系 gog 是一个面向 Google Workspa
llama.cpp Docker 部署:3 种配置从镜像启动到常驻健康检查
llama.cpp Docker 部署:3 种配置从镜像启动到常驻健康检查 llama.cpp 是一个纯 C/C++ 编写的 LLM 推理引擎,不依赖 Pyth
人工智能大模型模型推理服务推理引擎本地部署后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考