# 配置
`config/*` 是一个全局、持久化、响应式的键/值存储——相当于
Deft 版的 dconf 或 Windows 注册表。它与 `pub/*` 位于
同一层(在 Runtime 上,而非 TUI Workspace),因此在独立的
`deft` 二进制和 TUI 宿主中都可用。
键是使用 `/` 作为分隔符的层级字符串,以所属应用
为前缀以实现命名空间隔离:`log/max-lines`、
`editor/font`、`config/auto-save`。没有所属应用的
跨领域键位于顶层:`ui/theme`、`window/width`。
## `config/*` 标准库函数
| 函数 | 返回值 | 用途 |
|---|---|---|
| `[config/get "key"]` | 值或 nil | 读取 |
| `[config/set "key" value]` | 存储的值 | 写入 + 发布变更 |
| `[config/unset "key"]` | bool | 移除;以 `:value nil` 发布 |
| `[config/scan "prefix"]` | map | 所有以 prefix 开头的键 |
| `[config/keys "prefix"]` | list | 匹配的键名 |
| `[config/count]` | number | 键总数 |
| `[config/load ?path]` | bool | 加载 `.deft/config.dft` |
| `[config/save ?path]` | bool | 写入整个注册表 |
| `[config/save-overrides ?path]` | 写入数量 | 与默认值比较;仅写入已更改项 |
| `[config/save-map $map ?path]` | bool | 写入给定的 map |
## 前缀——手动指定,而非自动
应用声明一次前缀后重复使用。没有运行时魔法——
“我当前在哪个应用中?”这个问题在 CLI 脚本、托管多个应用的
TUI 面板以及子运行时之间确实存在歧义,因此
注册表从不尝试回答它。
def cfg "log/" # Explicit prefix at each call site — no hidden resolution config/set ($cfg + "max-lines") 1000 echo [config/get ($cfg + "max-lines")] # Or a thin wrapper when call-site noise matters def set-cfg {|k v| config/set ($cfg + $k) $v]} def get-cfg {|k| config/get ($cfg + $k)]} set-cfg "max-lines" 1000
跨应用读取保持显式(从 `log` 内部调用
`[config/get "editor/font"]`),而跨领域键(`ui/theme`)位于
顶层,无需任何扭曲。
## 响应式
`config/set` 和 `config/unset` 在 `"config"` 发布/订阅主题上
发布变更事件。订阅者在标准事件 map 的 `:data` 下
接收它:
%{ :type :message :topic "config"
:data %{ :key "log/max-lines" :value 1000 :old 500 } }
在处理函数中按键前缀订阅并过滤:
def on-config {|ev| @doc "React to log config changes." if [starts-with? $ev~data~key "log/"] { echo "log config changed:" $ev~data~key "->" $ev~data~value [reload-logger] | } } pub/subscribe "config" "on-config"
参见 Pubsub 了解代理契约。
## 值——仅限可字符串化的数据
注册表值必须能通过 `deft/stringify` /
`deft/parse` 往返转换:
- 标量(数字、字符串、布尔值、nil)
- 关键字
- 列表、映射、集合
- 带标签的 map
函数值以名称(字符串)形式存储,并在
调用点通过 `[apply $name ...]` 解析。`apply` 接受
字符串/关键字名称(通过命名空间链解析)或
直接的可调用值(闭包、native-func、关键字、map、
带标签的 map、集合、向量)。
## 持久化
`config/save` 将整个注册表写入一个扁平 map 文件(默认
`.deft/config.dft`),内容为 `%{ "key1" v1 "key2" v2 }`。`config/load`
读取此类文件并替换注册表内容。该文件是纯
Deft 数据——便于 diff、可手工编辑,且对版本控制
友好。
# Per-package defaults ship as small data files (keyword keys are idiomatic): %{ :log/max-lines 1000 :log/wrap true :log/severity :all }
`config/save-overrides` 与已加载的默认值进行 diff,仅写入
已更改的键——这是配置应用的规范保存路径。
加载同时接受字符串键和关键字键;保存输出字符串
键。
## 启动时加载默认值
典型包的 `on-init` 会在注册表为空时
加载默认值:
def on-init {|| @doc "Load log defaults on first run." if (empty? [config/scan "log/"]) { config/load "packages/log/defaults.dft" | } pub/subscribe "config" "on-config" }
## 配置应用
TUI 自带一个配置应用(见 `packages/config/src/config.dft`),
它会:
- 读取 `[config/scan $prefix]` 以列出键。
- 让用户切换布尔值、编辑字符串/数字。
- 通过 `config/set` 写回。
- 通过 `config/save-overrides` 持久化变更——仅保存用户
显式更改的键,而非整个注册表。
## 各项内容的归属
- `tui/get` / `tui/set`——每个会话短暂的 UI 状态(光标
位置、选择、滚动)。仅限 TUI,从不持久化。不同的
关注点;不要混淆。
- `config/*`——持久化的用户偏好。所有宿主均适用。通过
`config/load` / `config/save` 落盘。
## 示例
### 一个简单偏好
config/set "editor/font-size" 14 echo [config/get "editor/font-size"]] # => 14 config/unset "editor/font-size" echo [config/get "editor/font-size"]] # => nil
### 扫描命名空间
def log-config [config/scan "log/"] # => %{ :log/max-lines 1000 :log/wrap true :log/severity :all } for k [keys $log-config] { echo "$k = $log-config~$k" }
### 响应式主题切换
def apply-theme {|ev| if ($ev~data~key == "ui/theme") { echo "switching theme to $ev~data~value" [load-theme $ev~data~value] | } } pub/subscribe "config" "apply-theme" # Anywhere: config/set "ui/theme" "catppuccin-mocha"
## 快速参考
| 形式 | 用途 |
|---|---|
| `[config/get "key"]` | 读取 |
| `[config/set "key" value]` | 写入 + 发布 |
| `[config/unset "key"]` | 移除 + 发布 |
| `[config/scan "prefix"]` | 所有匹配的键 |
| `[config/keys "prefix"]` | 匹配的键名 |
| `[config/count]` | 键总数 |
| `[config/load ?path]` | 从磁盘加载 |
| `[config/save ?path]` | 全部写入磁盘 |
| `[config/save-overrides ?path]` | 仅写入与默认值的差异 |
| `[config/save-map $map ?path]` | 写入给定的 map |