首页
/ Zed 主题扩展开发指南:themes JSON 结构、设计实践与源码解析

Zed 主题扩展开发指南:themes JSON 结构、设计实践与源码解析

2026-09-06 18:14:36作者:钟日瑜

Zed 的主题系统允许通过扩展(Extension)分发一套或多套配色方案,本文以官方文档 extensions/themes.md 为骨架,系统讲解主题文件应放在扩展的哪个目录、JSON 的完整结构与每个关键字段的含义,并结合当前仓库内置主题与 extensiontheme 两个 crate 的源码佐证实际加载与渲染逻辑。读完后你可以仿照仓库自带的 One / Gruvbox / Ayu 主题,独立编写、本地调试并把自定义主题发布到 Zed 的扩展商店。

主题文件放在扩展的哪个目录

按官方文档的规定,扩展中的 themes 目录应当包含一个或多个主题文件,即结构为:

my-extension/
├── extension.toml
├── languages/
├── snippets/
└── themes/
    └── my-theme.json      # 主题定义

该目录约定与扩展的整体规范(developing-extensions.md)一致:Zed 扩展是一个包含 extension.toml 的 Git 仓库,可以同时提供语言、调试器、主题、图标主题、代码片段与 MCP 服务器等多种能力。

从源码可以确认,themes 目录并不是运行时随意扫描的,而是在构建扩展清单(manifest)时被一次性收集。在 crates/extension/src/extension_builder.rs 中可以读到如下逻辑:

  1. 拼接扩展根目录下的 themes 子目录;
  2. 遍历该目录的全部条目;
  3. 只收集扩展名为 .json 的文件,并把相对路径追加到 manifest.themes 列表中(若尚未存在)。

也就是说,扩展的 themes 目录下任何直接放置的 .json 文件都会被当作主题文件收录,文件名不限,只要内容是合法的主题 JSON。收集到的路径会写进扩展的 manifest,随后由主题系统加载并展示给用户选择。

每个文件需要遵循的 JSON Schema

官方文档明确:每个主题文件都应遵循 https://zed.dev/schema/themes/v0.2.0.json 对应的 JSON Schema。该 schema 通过文件顶部的 $schema 字段声明,这一写法同样出现在仓库自带的主题示例中,例如 assets/themes/one/one.jsonassets/themes/gruvbox/gruvbox.jsonassets/themes/ayu/ayu.json,它们的首行都是:

{
  "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
  ...
}

这三套主题文件是当前仓库中现成的、合法的 v0.2.0 实现样例,编写自定义主题时可以直接照抄其结构骨架再替换颜色值。

主题文件结构小结

一个 themes/*.json 文件由三个顶层部件组成(下文依次详解):

  • name:主题族(Theme Family)的名称;
  • author:主题族作者的名字;
  • themes:隶属于该主题族的一组主题(Theme)对象数组。

主题 JSON 的整体结构与 Theme Family

以仓库内 One Dark 主题的真实文件片段为例(assets/themes/one/one.json):

{
  "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
  "name": "One",
  "author": "Zed Industries",
  "themes": [
    {
      "name": "One Dark",
      "appearance": "dark",
      "style": {
        "background": "#3b414dff",
        "text": "#dce0e5ff",
        "text.accent": "#74ade8ff",
        "syntax": { ... },
        ...
      }
    },
    {
      "name": "One Light",
      "appearance": "light",
      "style": { ... }
    }
  ]
}

可以看到:

  • 外层对象是一个 Theme Family(主题族),包含 nameauthor,以及 themes 数组。
  • themes 数组里每一个元素是 Theme(具体的一套主题)。同一个文件可以声明一个族内多套主题(例如同族下同时发布 Dark 与 Light),用户仍可在设置中单独选用其中任意一套。
  • 每套 Theme 都包含 name(该套主题的名称)和 appearance(外观模式),以及承载全部颜色配置的 style 对象。

appearance 的取值

appearance 只有两个合法值:"light""dark"。这一点有源码直接佐证:在 crates/theme/src/schema.rs 中,序列化反序列化使用的枚举 AppearanceContent 只有 LightDark 两个变体,并以 snake_case 序列化,即对应 JSON 中的 "light""dark"

同时 crates/theme/src/theme.rs 也说明:appearance 决定整套主题用于亮色还是暗色界面,并影响 Zed 根据系统外观自动切换时的主题选择。用户在 Zed 设置中的 themetheme_dark 字段分别指定外观模式下使用的主题。

style 对象:UI 色彩体系详解

官方文档将 style 下的属性归纳为五大类。需要注意:文档里使用的是概括性说法(如 background / foreground / accent),而实际 v2.0.0 的 JSON 采用扁平的、带命名空间的键,把一套 UI 颜色通过语义化键名精确映射到 Zed 界面中的各种表面、控件与状态。真实主题文件中存在的核心命名空间如下:

基础主色

文档中的“背景 / 前景 / 强调色”对应到真实键名大致是:

  • background:整体背景色。在 One Dark 中为 #3b414dff
  • 前景文本色:实际键名为 text(例如 #dce0e5ff)及其衍生 text.mutedtext.placeholdertext.disabledtext.accent
  • 强调/高亮色:实际键名为 text.accenticon.accent,也可通过 accents 数组向系统提供一组可循环使用的强调色(见下文“accents 与多人协作用色”一节);
  • 图标色:iconicon.mutedicon.accent 等,与文字色族并列。

表面与元素(surfaces / elements)

文档举例中的 element.backgroundbordertext 等,对应真实文件里的完整键族:

  • 表面层级:surface.backgroundelevated_surface.background 分别描述普通面板与“抬升表面”(如弹层)的背景,Zed 用 background 定义窗口基底、用表面色逐层构建深度;
  • 元素色:element.background,并配套按交互状态区分的系列:element.hover(悬停)、element.active(按下/激活)、element.selected(选中)、element.disabled(禁用);同族还有 ghost_element.*(透明/幽灵按钮在悬停、激活、选中等状态下的色值);
  • 边框色:border 及其状态变体 border.variantborder.focused(聚焦边框)、border.selectedborder.transparentborder.disabled
  • 拖放目标:drop_target.background

面板与窗口组件

  • 状态栏与标题栏:status_bar.backgroundtitle_bar.backgroundtitle_bar.inactive_background
  • 工具栏与标签栏:toolbar.backgroundtab_bar.backgroundtab.active_backgroundtab.inactive_background
  • 面板:panel.backgroundpanel.focused_borderpane.focused_border(One Dark 中这两个边框值写为 null,表示不额外绘制聚焦边框);
  • 滚动条:scrollbar.thumb.backgroundscrollbar.thumb.hover_backgroundscrollbar.thumb.borderscrollbar.track.backgroundscrollbar.track.border
  • 搜索高亮:search.match_backgroundsearch.active_match_background(One Dark 中使用了带透明度的强调色,如 #74ade866)。

状态色族(status colors)

Zed 的 UI 大量使用语义化状态色表示 diff、错误与提示,键名均为 基础色 + 可选 .background/.border 后缀:

  • 版本控制:version_control.addedversion_control.modifiedversion_control.deletedversion_control.word_addedversion_control.word_deleted 以及 version_control.conflict_marker.ours / .theirs
  • 通用状态:createddeletederrorwarninginfohintmodifiedrenamedsuccesshiddenignoredunreachablepredictive(AI 预测文本),每个状态又带 .background.border 变体,用于状态徽标、面板底色与描边。

关于这些背景色的生成,crates/theme/src/fallback_themes.rs 里有一段值得留意的兜底逻辑:如果主题只自定义了某个状态色的“前景”(主体色)而没有自定义背景色,那么系统会取前景色并叠加 25% 透明度(fg_color.opacity(0.25))来生成背景色。因此编写主题时,状态背景色可以省略,Zed 会自动补齐;而希望精确控制外观时则应显式给出。

编辑器专属颜色(editor.*)

文档列出的 editor.backgroundeditor.guttereditor.line_number 对应真实键族:

  • editor.background:编辑器画布底色;
  • editor.foreground:编辑器中的默认前景文本色;
  • editor.gutter.background:行号槽(gutter)背景;
  • editor.line_number / editor.active_line_number / editor.hover_line_number:普通行号、当前行行号与悬停行号颜色;
  • editor.active_line.background:当前行高亮背景;editor.highlighted_line.background:高亮行背景;
  • editor.invisible:空白字符(空格、Tab)等不可见字符的颜色;
  • editor.wrap_guide / editor.active_wrap_guide:折行辅助线颜色;
  • editor.subheader.background:编辑器内子标题背景;
  • editor.document_highlight.read_background / write_background:同一符号的读引用/写引用高亮底色;
  • terminal.* 亦属编辑器家族,见下文终端配色节。

值得注意:这些带命名空间的键(editor.xxx)覆盖的是“通用 UI + 编辑器 chrome”,而真正的代码语法着色syntax 对象中定义,两者职责分开、互不混淆。

颜色值的书写规则

从仓库的样例主题可以总结出 v2.0.0 schema 中颜色值的书写习惯:

  • # 开头、十六进制 RGB 值,常见写法为 8 位 #RRGGBBAA(末尾两位是 alpha 透明度),例如 One Dark 的 "background": "#3b414dff"(完全不透明)、搜索高亮 "#74ade866"(66 即 40% 透明度);
  • 某些键允许省略 alpha,直接写 6 位 #RRGGBB,例如 One Dark 的行号 "editor.line_number": "#4e5a5f"
  • 值为 null 表示“不特别指定/交由默认”,例如 "panel.focused_border": null,以及在 syntax 内各 token 的 "font_style": null"font_weight": null(表示沿用默认字型与字重)。

在加载侧,crates/theme/src/schema.rstry_parse_color 展示了颜色解析链路:先把颜色字符串解析为 RGBA,再转换到 HSLA(色相/饱和度/明度/透明度)色彩空间交给渲染管线使用。

syntax 对象:语法高亮配色

style.syntax 是主题的核心部分——一个以语法元素为键的对象,每个键的值描述该 token 的颜色及可选的字形属性。以 assets/themes/one/one.json 为例,一个语法项包含三个字段:

"syntax": {
  "attribute": {
    "color": "#74ade8ff",
    "font_style": null,
    "font_weight": null
  },
  "boolean": {
    "color": "#bf956aff",
    "font_style": null,
    "font_weight": null
  }
}

其中 color 是必填的颜色值;font_style 可填 "italic" 等斜体样式、null 表示不修饰;font_weight 可填具体字重或 null。文档中提到的关键字、字符串、注释分别对应 keywordstringcomment 键。

综合仓库内 One Dark(syntax 区块从 one.json 开始)实际声明出的全部语法键,常见可用键包括:

  • 字面量/标识类:attributebooleanconstantnumberstringstring.escapestring.regexstring.specialstring.special.symbollabellink_textlink_urinamespaceoperatorproperty
  • 代码结构类:keywordfunctionconstructorenumtypevariablevariable.parametervariable.specialtagtitletext.literalembeddedpreprocprimaryvariantselectorselector.pseudo
  • 注释与标记类:commentcomment.docpunctuation 及其细分 punctuation.bracketpunctuation.delimiterpunctuation.list_markerpunctuation.markuppunctuation.special
  • 强调类:emphasisemphasis.strong
  • 特殊能力:hintpredictive(AI 补全预测文本)、diff.plusdiff.minus(行内 diff 增删字符)、link_text 等。

syntax 对象最终被映射为 Zed 的 SyntaxTheme 结构(见 crates/theme/src/styles/syntax.rssyntax_theme::SyntaxTheme 的重导出,其实现位于 crates/syntax_theme/src/syntax_theme.rs),再叠加语言语法树(Tree-sitter)中各 token 的捕获名完成着色。

命名空间式语法键的层级原则

观察上面列表可见,语法键同样支持点分命名空间的继承与回退:例如定义了 punctuation 后,punctuation.bracket 是它的特化;未显式定义的更细 token 会回退到较宽泛的父键颜色。因此设计主题时建议先确立基础键(keywordstringcommentfunctiontypeconstantvariablenumberpunctuationoperator)的整体调性,再针对语言差异补充 .special.escape 等特化键,使各语言渲染保持一致。

players 与 accents:多人协作用色

官方文档虽未展开,但真实 v2.0.0 主题文件中还包含两类与 Zed “multiplayer editor”基因强相关的配色:

  • players:一个颜色对象数组,用于多人在同一文件中协作时区分不同的参与者光标。每个元素包含 cursor(光标颜色)、backgroundselection(该参与者的选区底色)。One Dark 在 one.json 中为 8 位参与者分别配了互不相同的色调;local() 参与者(本机用户)的选区色还参与派生 UI 选中态默认色(见 crates/theme/src/fallback_themes.rs:若未显式设置 element_selection_background,则取本地参与者选区色并把全透明改为 25% 透明度);
  • accents:一个颜色数组,提供一组可循环取用的强调色(用于多光标、多选区、语法上色淡彩等需“区分数个实例”的场景)。Gruvbox 在 assets/themes/gruvbox/gruvbox.json 中给出七色列表:["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"]。如果你设计的主题没有显式提供 accentscrates/theme/src/fallback_themes.rs 中同样存在基于主强调色的默认色板兜底。

终端颜色:ANSI 配色

style 下还有一套为内置终端准备的 ANSI 颜色定义,键为 terminal.backgroundterminal.foreground(普通前景)、terminal.bright_foregroundterminal.dim_foreground,以及 8×3 的 ANSI 基本色:

  • 8 个标准色:terminal.ansi.blackredgreenyellowbluemagentacyanwhite
  • 每个标准色再派生亮色:terminal.ansi.bright_blackbright_white
  • 以及暗色变体:terminal.ansi.dim_blackdim_white

One Dark 的完整 ANSI 定义见 assets/themes/one/one.json,例如 red: #e06c75ffbright_red: #EA858Bffdim_red: #a7545aff。这套值决定终端中命令输出、git diff、日志等 ANSI 颜色渲染是否与你的主题协调一致;如果主题忽略它们,终端将退回到系统/默认 ANSI 配色。

设计与制作:从 Theme Builder 到本地参照

官方文档推荐使用 Zed 的 Theme Builder 网页工具来“基于某个现有主题”设计自己的主题:该工具允许你针对各 UI 表面(surfaces)微调并实时预览效果,满意后把结果导出为 JSON,再放入扩展的 themes 目录进行发布。

在没有联网可视化工具的情况下,更可靠的制作路径是以本仓库为参照

  1. 打开 assets/themes/one/one.jsonassets/themes/gruvbox/gruvbox.json,整体复制其骨架;
  2. 替换 backgroundtext、各 element.*border.* 与面板/编辑器系列色,先建立 UI 调性;
  3. 依第一节的键表精简或扩展 syntax 对象,逐项替换语法高亮;
  4. 若需要内置终端观感一致,继续调整 terminal.ansi.*;若涉及多人与 AI 功能,再补 playersaccents
  5. 对照示例确认键名拼写、颜色值格式(6 位或 8 位 hex、允许 null)与 appearance 取值无误。

关于本地调试的另一个线索:Zed 自带的默认主题族(zed_default_themes)在 crates/theme/src/fallback_themes.rs 中以代码形式定义,包含默认暗色主题与 default_color_scales() 派生出的完整色板。当你自定义主题漏写某个键时,加载逻辑会先尝试语义推导(如用前景推导背景),再回退到这些默认值,保证任意不完整的主题也不会导致界面颜色缺失。

安装调试与发布到扩展商店

本地作为 Dev Extension 使用

在开发过程中不需要先把主题发布出去即可试用。参考 developing-extensions.md 中的说明:

  1. 在扩展目录写好 extension.tomlthemes/*.json
  2. 在 Zed 的 Extensions 页面点击 Install Dev Extension(或执行 zed::InstallDevExtension action),选择扩展根目录;
  3. 若已安装同名已发布版本,它会被自动卸载,安装后 Extensions 页面会显示 “Overridden by dev extension”;
  4. 安装完成后,通过命令面板的主题选择器(Theme)即可看到该主题族下的各套主题并切换预览。

一个最小可用的 extension.toml 形如(详细字段说明见 developing-extensions.md):

id = "my-theme-extension"
name = "My theme extension"
version = "0.0.1"
schema_version = 1
authors = ["Your Name <you@example.com>"]
description = "A custom theme for Zed"

注意:纯主题扩展一般无需任何 Rust 代码,因为主题只是静态 JSON 资源,Zed 会以 crates/extension/src/extension_builder.rs 中的方式构建 manifest 并加载。

发布到扩展商店

主题验证无误后即可准备发布:维护好仓库中的 extension.tomlthemes 目录与 CHANGELOG,再按 Zed 扩展商店的发布流程提交版本(Zed 的官方扩展商店管理着社区主题扩展仓库,相关说明位于仓库的 publishing 文档目录中)。用户侧则通过 Extensions 面板搜索、安装已发布的主题扩展,安装后即可在主题选择器中切换——整体体验与 Zed 内置的 One、Gruvbox、Ayu 等主题一致,因为它们在当前仓库中同样以 assets/themes 下的 v0.2.0 JSON 形式维护,走的正是本文描述的同一套结构。

小结:写一个主题的检查清单

  • 文件放在扩展根目录的 themes/ 下,任意 .json 都会被 manifest 收录;
  • 顶层提供 $schemanameauthorthemes 数组,themes 内每个元素有 nameappearancelight/dark)和 style
  • style 中按需覆盖:background / text / text.accentsurface.*element.*(含 hover/active/selected/disabled)、border.*、面板与滚动条、editor.*、状态色族(error/warning/info/modified/predictive 等)、terminal.ansi.*playersaccents
  • syntax 内用点分命名空间的键(keywordstringcommentfunctiontypevariablepunctuation.* 等)精确着色,每项含 color,可选 font_stylefont_weight
  • 颜色值支持 6/8 位十六进制,允许 null 委托默认值;
  • 不确定的键名与色值可随时对照 assets/themes/one/one.json,并使用 Theme Builder 或 Dev Extension 即时预览,最终再发布到扩展商店。

遵循以上步骤与仓库中现成样例,你就能从零产出一套结构正确、可本地预览、可上架发布的 Zed 主题扩展。

登录后查看全文
热门项目推荐
相关项目推荐