# 预置文件 预置文件(prelude)是 Deft 的项目级配置文件。它位于 `.deft/prelude.dft`,由项目中的每个运行时自动加载——脚本二进制、 TUI 宿主(`retro`/`retra`),以及你创建的任何子运行时。它是一个 普通的 Deft 数据文件,顶层形式是单个 `%Prelude{...}` 标记映射。
%Prelude{
    :mounts   %{ "stdext"  ".deft/packages/deft/stdext/0.2.0/src"
                 "launcher" ".deft/packages/deft/stdext/0.2.0/src" }
    :pkgs     @{ ".deft/packages" }
    :registry "https://packages.deft-lang.org"
    :themes   %{ :default "Dracula" }
    :font     %{ :family "JetBrains Mono" :size 16 }
    :window   %{ :width 2200 :height 1200 }
    :workspace %{ :default "ide" }
    :keys     %{ :prefix "ctrl-b" }
}
`deft init` 会为你写入一个模板预置文件。编辑它以自定义挂载、 包根目录、注册表、主题和 TUI 默认值。 ## 预置文件的加载时机 宿主运行时很早就加载预置文件——在任何用户代码运行之前、TUI 工作区构建之前: 1. 运行时启动;`settle()` 注册内置标准库函数。 2. `loadPrelude()` 从当前工作目录读取 `.deft/prelude.dft`。如果文件 不存在或无法解析,预置文件为 `nil`,每个字段都回退到其内置 默认值。 3. 来自 `:mounts` 的挂载和来自 `:pkgs` 的包根目录注册到 VM 上。 4. `:registry` 配置包插件的 URL。 5. 配置注册表从每个 `:pkgs` 根目录自动加载各包的默认值,然后在 其上叠加 `.deft/config.dft`。 6. 仅 TUI: 读取 `:window` 以设置窗口大小,并将 `:font` 解析为 字体文件(在 Landlock 沙箱生效之前进行,以便扫描字体目录); `:themes`、`:workspace` 和 `:keys` 会被暂存,供工作区构建器 使用。 7. 用户代码运行(启动的文件、REPL 输入或工作区规格求值)。 静默无操作语义:预置文件缺失或格式错误不算错误。宿主使用默认值 继续运行。这意味着脚本即使在未初始化的项目之外运行也能工作。 ## 字段 ### `:mounts` — 挂载别名 一个从挂载名 → 目标的映射。每个条目注册一个挂载: `import "<name>/..."`(以及任何以 `<name>/` 开头的 `:source` 路径) 在解析之前被重写为真实路径。挂载名可以是多段的——像 `"deft/stdext"` 这样的包引用可以正常工作,导入会优先匹配最长的 挂载前缀。 值的类型决定挂载的含义: - 字符串值——导入挂载(经典形式):该名称映射到一个本地目录, 用于 `import` 和 `:source` 解析。 - 映射值(或 `%Local{}` / `%Ssh{}` 标记映射)——VFS 挂载: 以挂载名作为开头段的 `fs/*` 路径会路由到其驱动(本地锚点目录或 通过 ssh 连接的远程主机)。绝不是导入挂载。
:mounts %{
    "deft/stdext" ".deft/packages/deft/stdext/0.2.0/src"
    "apps"        "packages"
    "themes"      "${HOME}/.config/deft/themes"

    # VFS mounts — fs/* paths under the name route to the driver
    :docs %Local{ :root "/usr/share/doc" }
    :prod %{ :kind :ssh :host "10.0.0.5" :root "/srv/app" :user "root" }
}
[fs/read "docs/misc.txt"]          # → /usr/share/doc/misc.txt
[fs/read "prod/etc/hostname"]      # → /srv/app/etc/hostname on 10.0.0.5
完成上述配置后,来自 Deft 代码的 `import "deft/stdext/html"` 和 工作区配置中的 `:source "apps/editor/src/editor.dft"` 都会通过配置的 目录解析。VFS 挂载则服务于 `fs/` 命名空间——有关路径语义、ssh 后端和运行时 `[vfs/mount]` API,请参阅 VFS。 键可以是字符串或关键字(`"deft/stdext"` 和 `:stdext` 都可用); 值必须是字符串(导入挂载)或映射(VFS 挂载)。路径字面量按原样 处理——语言层面没有 `$HOME` 展开,但宿主的标准 `[fs/expand-path]` 会在挂载注册时运行。从预置文件植入的 VFS 挂载会进入进程全局表, 因此跳过预置文件加载的应用线程也能看到它们;请注意 `:kind :ssh` 挂载需要子进程和网络访问,而 Landlock 沙箱会拒绝这些访问(沙箱 处于活动状态时会打印警告)。 显式的 `:mounts` 条目总是优先于包自动扫描(下一节)——用它们将 包固定到特定的检出。`deft init` 会搭建一个 `launcher` 挂载(用于 解析 TUI 启动器——见下文 [`:launcher`](#launcher));stdext 包的 挂载不需要预置文件条目。 ### `:pkgs` — 包搜索根目录 用于扫描包的目录向量。第一个条目是 `[pkg/install]` 使用的安装 目标。条目可以是相对路径(相对于当前工作目录解析)。
:pkgs @{ ".deft/packages" }
也接受单个字符串:
:pkgs ".deft/packages"
启动时会扫描每个根目录,找到的每个包(开发检出或已安装版本)都会 自动挂载到 `owner/name` 下,此外还有 `owner/name@version` 固定版本 和 `@latest` 别名——无需 `:mounts` 条目。根目录按列出顺序扫描; 第一个提供该包的根目录获胜。这让你可以将项目本地的包目录叠加在 共享的系统目录之上。显式的 `:mounts` 条目会覆盖扫描结果。 ### `:registry` — 包注册表 URL 当设置为 `http://` 或 `https://` URL 时,`pkg/*` 标准库函数 (`pkg/install`、`pkg/fetch`、`pkg/info`、`pkg/available`)会与该 注册表通信,而不是内置的本地 virt-pkg 树。除 HTTP(S) URL 之外的 任何内容都会被忽略。
:registry "https://packages.deft-lang.org"
用它来将项目指向私有注册表(通常由 nginx 终止 TLS,提供静态包 tarball)。 ### `:themes` — TUI 主题默认值 一个带有 `:default` 键的映射,指定当加载的工作区配置未指定自己的 `:theme` 时要应用的主题。
:themes %{ :default "Dracula" }
TUI 宿主会在常规主题位置(用户覆盖、stdext 默认值、旧版 `./themes/`)搜索与该名称匹配的文件。工作区级的 `:theme` 会覆盖 此项;见下文工作区配置和 TUI 文档。 ### `:window` — TUI 初始窗口大小 TUI 窗口的像素尺寸。仅 `retro` 构建(OpenGL 宿主)遵循此项; `retra` 受终端模拟器约束。
:window %{ :width 2200 :height 1200 }
两个键都是可选的;缺少的尺寸会回退到二进制编译时的默认值。 ### `:font` — TUI 字体(GL 宿主) GL 宿主的工作区单元格网格、面板窗口和表面文本(`draw/text-px`) 背后的等宽字体。`retro` 和 `hydra` 遵循此项;`retra` 通过 终端模拟器的字体渲染,忽略此键。
:font %{ :family "JetBrains Mono" :size 16 }     # resolve by family name
:font %{ :path "/home/u/.fonts/FiraCode.ttf" :size 16 }  # exact file
:font "Source Code Pro"                          # family shorthand
- `:family` 扫描常规字体目录(`$XDG_DATA_HOME/fonts`——否则 `~/.local/share/fonts`——然后是 `~/.fonts`、`/usr/local/share/fonts`、 `/usr/share/fonts`;递归,`.ttf`/`.otf`/`.ttc`),并按规范化文件名 选择最佳匹配,优先 `Regular` 字重。`:path` 使用精确文件 (相对路径相对于工作目录解析),当两者都存在时优先于 `:family`。 - 候选字体必须是有效等宽的:声明为固定宽度,或具有均匀的字形 步进(有些真正的等宽字体——例如 Google 的 Noto Mono TTF——并未 声明该标志)。探测还会栅格化探测字形,并要求每个位图适配其 单元格并映射到真实且互不相同的拉丁字形,因此比例字体(以及无 拉丁字符的脚本字体,它们会渲染为方框)会被拒绝并给出警告; 单元格网格以固定单元格宽度步进,因此它们会渲染错误。 - `:size` 是像素大小,限制在 6–128 之间(默认 18)。 - 失败时会在 stderr 上发出警告(`font: ...`)并回退到 DejaVu Sans Mono。成功解析会记录 `font: using <path> (prelude :font ...)`,因此可以从终端确认所选 字体。 - CLI 标志 `--font` / `--size` 按字段优先——预置文件是项目默认值, 标志是每次调用的覆盖。 任意字符,无需重新构建。 栅格化的字符集并非预先烘焙:除启动集 (ASCII + 制表符 + UI 符号装饰)外,其他每个码点在首次出现时按需 栅格化——西里尔文、希腊文、CJK、emoji、任何符号——并缓存在可增长 的图集页中。两个可选键可以调节这一点:
:font %{
    :family "JetBrains Mono" :size 16
    :fallback @{ "/usr/share/fonts/.../NotoSansMonoCJK-VF.ttc" }  # faces tried for chars the primary lacks
    :ranges   @{ "0x0400-0x04FF" "0x4E00-0x9FFF" } # pre-rasterize hot scripts
}
- `:fallback`(字符串或字体族名/文件路径的向量,最多 4 个)注册 一些字体面,当主字体缺少某个码点时按顺序查询——典型形态是拉丁 等宽主字体加一个 CJK/符号回退字体。等宽探测不适用于回退路径; 只有主字体驱动单元格网格。注意字体族匹配器评分的是文件主干名 (file stem),而非字体元数据——文件名带样式后缀的 CJK 字体集 (`NotoSansMonoCJK-VF.ttc`)通过精确的 `:path` 解析更可靠。 - `:ranges`(字符串或 `first-last` 十六进制(可带 `0x` 前缀)或 裸码点的向量)在启动时将范围预栅格化到字形缓存中,这样首次使用 时不会在帧中途承受小的栅格化卡顿。纯粹是优化——无论如何所有 内容都按需渲染。 - 这两个键始终来自预置文件(CLI `--font` 仅选择主字体面),并且 两者都会提供给沙箱的只读授权。 - 东亚宽字符脚本(CJK、假名、谚文、全角)每个字形占两个单元格—— 布局混合脚本文本时,用 `[width $s]` 而不是 `len` 测量显示宽度。 交互式缩放。 在 `retro` 和 `hydra` 中,`Ctrl+=` / `Ctrl+-` (小键盘 `+`/`-` 以及用于 `+` 字形的 `Ctrl+Shift+=`)可逐步调整 字体大小,`Ctrl+0` 将其重置为启动时的大小——浏览器风格,对对话框 和应用全局生效。`retro` 会同时缩放网格和每个弹出的面板窗口; `hydra` 缩放整个实例(新窗口继承缩放后的大小)。缩放仅在当前会话 有效——用这里的 `:size` 持久化大小。 从代码应用。 字体也可以通过标准库动词实时更改(解析和等宽 规则与此键相同):
[tui/set-font "JetBrains Mono" 16]              # family (or file path) + size
[tui/set-font %{:family "JetBrains Mono" :size 16}]
[tui/set-font %{:size 20}]                      # size-only (keeps the font)
[tui/font]                                      # => %{:path "..." :size 16}
[tui/font-list]                                 # => @("Adwaita Mono" "Deja Vu Sans Mono" ...)
解析和固定宽度检查会立即运行(当字体族无法解析时,该调用返回 `false`);重建在下一个宿主帧生效——网格、弹出窗口和表面文本都会 跟随。将 `tui/font-list` 与 `dialog/search` 搭配,可以在你自己的 应用中实现字体选择器。与缩放一样,应用仅在当前会话有效——用这里 的 `:font` 持久化选择。仅限 GL 宿主(`retro`/`hydra`;`retra` 通过 终端模拟器渲染)。`tui/set-font` 更改主字体面和大小; `:fallback`/`:ranges` 由预置文件配置,并在应用后继续保留。 ### `:workspace` — 默认工作区(TUI) 当 TUI 宿主启动时没有显式指定 `--workspace` CLI 标志,它会在此处 查找要自动加载的工作区配置。
:workspace %{ :default "ide" }
这会加载 `.deft/workspaces/ide.dft`(工作区规格格式见 TUI:应用平台)。 ### `:keys` — 工作区键绑定(TUI) TUI 宿主的键绑定。目前有一项设置:`:prefix`,即激活工作区命令的 组合键(后续按键选择命令——`v`/`h` 拆分、`x` 关闭、`t` 循环主题、 `d` 停靠、`p` 弹出等)。
:keys %{ :prefix "ctrl-b" }      # tmux-style (default)
:keys %{ :prefix "ctrl-a" }      # screen-style
:keys %{ :prefix :ctrl-space }   # keyword form
:keys %{ :prefix "off" }         # disable the prefix chord
修饰键(`ctrl`、`alt`、`shift`)位于键名之前,以 `-`、`_` 或 `+` 分隔;键名是字母(`ctrl-b`)、数字(`ctrl-1`)、`space`、`slash`、 `tab`、`enter`、`escape` 或 `f1`..`f12`。值可以是字符串或关键字。 未知或缺失的值保留内置的 Ctrl+B 默认值。 实时配置键 `ui/prefix` 会覆盖预置文件——用 `[config/set "ui/prefix" :ctrl-a]`、配置应用或 `.deft/config.dft` 设置它,它会立即生效(无需重启)。预置文件提供项目默认值。两个 TUI 宿主(`retro` 和 `retra`)都遵循该组合键;后续命令保持不变。 ### `:sandbox` — Landlock 沙箱路径列表 当启用 Linux 沙箱(`DEFT_SANDBOX=1`)时,`:sandbox` 映射的只读和 可写路径列表通过 Landlock 限定进程的文件系统访问:
:sandbox %{
    :readonly @{ "/etc" "/usr/bin" "/usr/share" "/proc" }
    :writable @{ "~/.config" "~/.local" }
}
应用沙箱时,`~` 会展开为 `$HOME`。可以手动编辑列表,或使用 `deft sandbox add/remove/list`。`deft init` 会写入一个模板块。完整 说明——始终授予的权限、内核要求和限制——请参阅 Sandbox。 ### `:launcher` — 启动器应用(TUI) 启动器是在新创建的面板中打开的应用——拆分(`Ctrl+B v` / `Ctrl+B h`)、添加分组,以及底部任务栏 Workspace 菜单中的 Workspace → New。TUI 宿主启动源路径 `launcher/launcher.dft`, 通过 `:mounts` 中的 `launcher` 挂载解析:
:mounts %{
    "stdext"  ".deft/packages/deft/stdext/0.2.0/src"
    "launcher" ".deft/packages/deft/stdext/0.2.0/src"
}
`deft init` 会搭建两个指向已安装 stdext 的挂载,因此无需进一步 配置即可找到内置选择器。启动启动器时的解析顺序: 1. 存在顶层 `:launcher "path/to/app.dft"` 字段时——一个完整源路径, 像其他路径一样通过 `:mounts` 挂载解析。 2. 当文件存在时,通过 `launcher` 挂载解析 `launcher/launcher.dft`。 3. 回退:通过 `stdext` 挂载解析 `stdext/launcher.dft`(内置选择 器)。 替换启动器。 启动器本身只是一个 Deft 应用,因此你可以将它 换成自己的——领域特定的仪表盘、kiosk 模式、公司品牌的选择器。 有两种方式:
# 1. Repoint the launcher mount at your app's directory
#    (convention: the file is <dir>/launcher.dft)
:mounts %{ "launcher" "myapp" }

# 2. Or set the explicit field for a full source path
:launcher "myapp/picker.dft"
显式字段优先;挂载是惯用形式,因为同一别名还服务于 Deft 代码中的 `import "launcher/..."`。构建自定义启动器见 TUI:应用平台。 ## 运行时访问 已加载的预置文件可在运行时通过 `prelude` 标准库函数访问:
echo [prelude]
# => %Prelude{ :mounts {...} :pkgs [...] :registry "..." ... }

# Read a single field
echo [prelude]~registry                # => "https://packages.deft-lang.org"
echo [prelude]~themes~default          # => "Dracula"

# Returns nil when no prelude is loaded (running outside a project)
echo [prelude]                         # => nil
这是脚本或应用在不重新读取文件的情况下发现项目配置的规范方式。 ## 预置文件不是什么 - 不是用户设置。 每用户偏好(用户级别的主题覆盖、键绑定、 编辑器选项)属于 `[config/*]`——见 Config。 - 不是机密。 预置文件是普通的 Deft 数据,通常会检入版本控制。 不要把 API 密钥或密码放在这里。 - 不是环境特定的。 预置文件随项目一起发布;如果你需要特定于 机器的覆盖(CI 与笔记本、生产与开发),请在应用代码中通过 `os/env` 查找来表达,或使用叠加在 `config/load` 之上的 `config/*` 注册表。 ## 另见 - 运行时 — 每个运行时在启动时加载预置文件 - VFS — 映射值的 `:mounts` 条目声明 VFS 挂载 - 配置 — 运行时键/值偏好,叠加在上层 - 沙箱 — `:sandbox` 键配置 Landlock 路径列表 - TUI:应用平台 — TUI 工作区配置 - 工具:包 — `pkg/*` 标准库函数和 注册表