Foam Smart Folders 完全指南:用保存的查询在 VS Code 侧边栏打造动态笔记视图
Foam Smart Folders 完全指南:用保存的查询在 VS Code 侧边栏打造动态笔记视图
导读
Smart Folders(智能文件夹)是 Foam 在 VS Code 资源管理器中呈现"已保存查询"的方式:每个智能文件夹都是一个存放在 .foam/queries/<id>.yaml 下的查询文件,按需实时执行并把匹配到的笔记以树形或列表形式展示在侧边栏。本文将以 docs/user/features/smart-folders.md 为核心,结合 foam-vscode 与 foam-core 的源码实现,系统讲解智能文件夹的创建、编辑、删除、视图选项,以及底层查询语法与存储机制,帮助你在大规模工作区中摆脱僵化的标签层级,用查询代替手动的目录组织。
Smart Folders 是什么
Smart Folders 的核心思想是:文件夹不存储文件,只存储"筛选条件"。它由三部分组成:
- 一个 YAML 查询文件(位于工作区
.foam/queries/目录,文件名即查询 id); - Foam 查询引擎(
@foam/core中的executeQuery)——按查询描述符在工作区中筛选笔记; - VS Code 树视图(
SmartFoldersProvider)——把匹配结果渲染到侧边栏。
典型场景是:创建一个名为 "Work in Progress" 的智能文件夹,只显示打了 #wip 标签、且没有 #archive 标签的笔记,见 foam-queries 中保存查询的示例。这样笔记物理上可以分散在任何目录,但视图上始终聚合在同一个逻辑分组下,非常适合跨目录检索大型工作区。
从源码结构看,智能文件夹与内联查询(foam-query 代码块)共享同一套查询语法和引擎:saved-store.ts 中的 QueryStore 负责读写 .foam/queries/ 下的 YAML 文件,而树视图直接调用 @foam/core 的 executeQuery 执行查询(见 smart-folders-explorer.ts),因此"保存的查询"和"内联查询"可以互相复制、完全等价。
创建 Smart Folder
通过面板或命令创建
创建智能文件夹有两种入口(见 create-smart-folder.ts):
- 打开资源管理器中的 Smart Folders 面板,点击 Create Smart Folder(对应命令
foam-vscode.views.smart-folders.create); - 或在命令面板运行 Foam: Create Smart Folder。
创建流程分两步交互:
- 输入名称(例如
Work in Progress); - 多选要包含的标签(可选,按 Esc 跳过,直接进入 YAML 手动编辑)。
完成后 Foam 会在 .foam/queries/<name>.yaml 生成查询文件并自动打开编辑。
名称如何变成文件名
输入的名称会先经过 sanitizeQueryId 处理成文件系统安全 id(实现见 saved.ts):
- 全部转小写;
- 空白与非法字符替换为
-; - 去掉首尾的
-,合并连续-。
例如 Work in Progress → work-in-progress.yaml,research/animals → research-animals.yaml。如果生成的 id 已存在,创建逻辑会自动追加序号(-2、-3……)直到不冲突(见 create-smart-folder.ts)。若名称无法产生有效 id(例如只含符号),Foam 会提示改用字母、数字、连字符或下划线。
标签选择背后的过滤逻辑
多选标签后,Foam 会构建一个"或"过滤器(buildOrFilter):
- 只选 1 个标签 →
filter: { tag: "#wip" }; - 选择多个标签 →
filter: { or: [ { tag: "#a" }, { tag: "#b" } ] },即匹配任意一个所选标签的笔记都会出现; - 工作区没有任何标签,或按 Esc 跳过选择 → 生成
filter: "*"(匹配全部笔记),留待你在 YAML 中细化。
生成的文件随后通过 QueryStore.save 写入磁盘(见 saved-store.ts),文件名由 id 决定,因此重命名文件即重命名查询。
编辑 Smart Folder
编辑有两种方式:
- 点击面板中智能文件夹旁边的铅笔图标(命令
foam-vscode.views.smart-folders.edit),会自动打开对应的 YAML 文件; - 直接打开
.foam/queries/<id>.yaml手动编辑。
改动会自动生效:SmartFolderStorage 为 .foam/queries/*.{yaml,yml} 注册了 FileSystemWatcher,文件创建、修改、删除都会触发树视图刷新(见 smart-folder-storage.ts 与 index.ts)。此外,工作区图谱(foam.graph)的更新同样会触发刷新,因此笔记标签、链接变化后智能文件夹的结果也会随之更新。
YAML 文件结构
一个保存的查询文件把查询字段直接放在 YAML 根级,可选两个包装字段:
name: Work in Progress # 可选;缺省时由文件名转成人类可读形式
description: Notes I am editing # 可选
filter:
and:
- tag: "#wip"
- not:
tag: "#archive"
sort: title ASC
limit: 50
最简有效文件只需一行:filter: "#wip"。
字段说明(完整语法见 foam-queries):
filter:选择包含哪些笔记;select:选择展示哪些字段(默认title和path);sort:排序,例如title ASC或backlink-count DESC;被排序的字段需同时出现在select中,否则投影后不可用;limit/offset:限制返回条数 / 跳过前 n 条;format:list、table或count。
从解析器源码(saved.ts)看,根级只允许 name、description 以及 filter、select、sort、limit、offset、format 这些字段,出现未知字段会被记为解析警告(warning 而非硬错误),并在树视图中以"errors"条目呈现。
filter 支持的筛选键
tag:拥有该标签的笔记(如tag: "#research");type:指定类型的笔记(如type: "daily-note");path:路径匹配正则的笔记(如path: "^/projects/");title:标题匹配正则的笔记;links_to/links_from:链接到 / 被链接自某笔记 id 的笔记,可用"$current"指代当前笔记;jexl:针对每条笔记求值的 Jexl 表达式(如"resource.tags|length > 2"),可访问resource对象及length、lower、upper等内建变换;and/or/not:逻辑组合;- 简单快捷形式:
"#tag"、"[[note-id]]"、"/regex/"、"*"(全部笔记)。
删除 Smart Folder
点击智能文件夹旁的垃圾桶图标并确认即可删除,对应的 YAML 文件会被移除。源码实现(create-smart-folder.ts)会先弹出模态确认框,只有选择 Delete 才调用 storage.delete(id);QueryStore.delete 会检查文件存在再删除(见 saved-store.ts)。删除后 FileSystemWatcher 的 onDidDelete 事件会清掉内存缓存并刷新视图。
视图选项
面板标题栏提供两个开关:
- Group By Folder / Flat list:把匹配的笔记按工作区路径分组为目录树,或以扁平列表展示。该偏好通过
ContextMemento持久化在foam-vscode.views.smart-folders.group-by状态中(默认folder,见 smart-folders-explorer.ts),由命令foam-vscode.views.smart-folders.group-by:folder和group-by:off控制; - Refresh:按需重新执行所有查询。
refresh()会清空结果缓存(uriCache)并重建树(见 smart-folders-explorer.ts)。
树视图本身由 SmartFoldersProvider 驱动(smart-folders-explorer.ts),其行为要点:
- 顶层条目:每个已保存查询一个条目,图标为
folder-library,描述显示匹配笔记数;若文件存在解析错误,则显示为带警告图标、描述为errors的SmartFolderErrorTreeItem,点击可直接打开查询文件(见 smart-folders-explorer.ts); - 分组模式:
buildFolderTree把匹配 URI 按工作区相对路径拆分为嵌套目录节点,每个目录节点显示叶子数,子项按名称排序(smart-folders-explorer.ts); - 错误隔离:单个查询执行失败不会拖垮整个面板——异常被捕获并记录日志,该文件夹显示为空结果(对应测试见 smart-folders-explorer.spec.ts);
- 信任工作区:查询可包含 Jexl 表达式,仅在受信任(trusted)工作区执行,与
foam-query块的信任门槛一致,未信任时默认() => false保证安全(见 smart-folders-explorer.ts)。
底层存储与文件约定
理解以下几点可以更安全地手工管理智能文件夹:
- 目录与扩展名:查询文件固定存放在
.foam/queries/,glob 为*.{yaml,yml}(常量QUERIES_GLOB,见 saved-store.ts);注意这是查询专用目录,Foam 普通笔记数据存储通常只覆盖**/*.md,不会误扫到这些 YAML; - id 来自文件名:查询 id 由文件名(去掉
.yaml/.yml扩展名)推导(idFromQueryFilename),不写在 YAML 内容里;重命名文件即重命名查询(见 saved.ts); - 显示名缺省规则:YAML 中省略
name时,显示名由 id 经humanizeQueryId自动生成,例如work-in-progress→Work In Progress;序列化时若name与缺省值相同会被省略(saved.ts); - 缓存与观察:
SmartFolderStorage在内存中缓存解析结果,配合FileSystemWatcher增量更新,避免每次刷新重读所有 YAML(smart-folder-storage.ts);查询执行结果也有uriCache,在每次refresh()时清空(smart-folders-explorer.ts)。
与 Foam Queries 的关系
智能文件夹与 Foam Queries 是同一能力的两种形态:
- 内联形态:在笔记中写
foam-query代码块,结果渲染在 Markdown 预览中; - 持久形态:把同样的字段写入
.foam/queries/下的 YAML,侧边栏的 Smart Folders 面板即为其展示层。
两者的语法完全一致,保存的查询文件与内联块可以互相复制。区别在于渲染位置与用途:智能文件夹适合常驻侧边栏的"工作视图"(如进行中的项目、待办聚合),内联查询适合放在某篇笔记里按需展示。若需要把查询结果嵌入到其他笔记,可参考 embeds。
小结
Smart Folders 把"组织笔记"从手工建目录、维护层级,转变为编写并保存查询:创建、编辑、删除、视图切换四个核心操作均可在面板内完成,所有改动即时生效;底层由 foam-core 的 QueryStore + executeQuery 与 foam-vscode 的树视图、文件监听器协作实现。掌握 查询语法(filter 组合、排序、limit、Jexl 表达式等),即可组合出如"#wip 且非 #archive""指向某主题的所有笔记""按回链数排序的 TOP 10"等动态视图,让侧边栏真正反映笔记内容而非目录结构。