首页
/ Windows Terminal 默认配置文件设计:profiles 的 defaults 与 list 结构是如何确定的

Windows Terminal 默认配置文件设计:profiles 的 defaults 与 list 结构是如何确定的

2026-09-06 11:05:29作者:幸俭卉

本篇围绕 Windows Terminal(仓库中的 Cascadia / 新一代终端设置模型)的设计规格文档 [#2325 - Default Profile Settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#2325 - Default Profile Settings.md) 展开:它记录了终端设置模型中“如何为所有配置文件(Profile)提供公共默认值”这一需求的完整设计过程——从四个候选方案(defaultSettings 全局对象、__default__ 魔法 GUID、把 profiles 改为含 defaultslist 的对象、inheritFrom 配置继承)的利弊权衡,到最终被采纳的方案 3,以及该方案在当前仓库源码中的真实落地方式(向后兼容的运行时类型判别、baseLayerProfile 基础层配置、IInheritable 继承树)。读完本文,你可以理解终端 JSON 设置中 profiles 对象结构的由来、四个方案被否决/采纳的具体原因,并能在自己的终端设置文件中正确使用 defaults 公共配置块。

背景:为什么需要“默认 Profile 设置”

规格文档的摘要部分点明了核心诉求:用户经常有一些希望在所有 Profile 上统一生效的公共设置(例如亚克力背景、字体、字号),而不想逐个 Profile 手工重复配置。文档作者为 Mike Griese(@zadjii-msft),创建于 2019-11-13,对应 issue 编号 #2325。

文档还交代了这个设计讨论的由来:在实现该功能的原始 PR(#3369)评审过程中,团队对“如何把这个能力暴露给用户”产生了分歧,于是先写规格、穷举方案、再做决策——这也是本文值得阅读的地方:它完整保留了四个候选方案的 JSON 形态、各自的优势与顾虑,而不只是给出结论。

方案一:全局设置中的 defaultSettings Profile 对象

第一个提案是:在根设置对象里增加一个 defaultSettings 属性,用一个“漂浮”在全局层的 Profile 对象承载所有默认值。完整示例如下:

{
    "$schema": "https://aka.ms/terminal-profiles-schema",
    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
    "defaultSettings":
    {
        "useAcrylic": true,
        "acrylicOpacity": 0.1,
        "fontFace": "Cascadia Code",
        "fontSize": 10
    },
    "requestedTheme" : "dark",
    "showTabsInTitlebar" : true,
    "profiles":
    [
        {
            "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "name": "Windows PowerShell",
            "commandline": "powershell.exe",
            "hidden": false
        },
        {
            "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
            "name": "cmd",
            "commandline": "cmd.exe",
            "hidden": false
        }
    ],
    "schemes": [],
    "keybindings": []
}

优势

  • 封装清晰:所有默认 Profile 设置集中在一个对象里,扫一眼设置文件就能定位默认值在哪里。
  • 易于理解:只有一个对象作用于其后所有 Profile,语义直白。

顾虑

  • 命名困难:团队对属性名反复权衡却始终没有满意答案:defaultSettings 容易与 defaults.json 概念混淆;defaultProfileSettings 会被理解为“默认那个 Profile 的设置”;defaults 同样与 defaults.json 冲突;baseProfileSettings 不够直观;profiles.defaultsinheritedSettingsrootSettingsglobalSettingsprofileSettingsprofilePrototype 等候选也都不被看好。
  • 全局层里为何多出一个“悬浮 Profile”? 用户可能会困惑:这个 Profile 对象为什么会出现在全局设置里?它难道就是默认 Profile 吗?

方案二:用户 profiles 列表中的 __default__ Profile 对象

第二个提案是:不新增全局属性,而是在 profiles 数组里放一个 GUID 为 __default__ 的特殊 Profile,它承载所有公共默认值:

{
    "$schema": "https://aka.ms/terminal-profiles-schema",
    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
    "requestedTheme" : "dark",
    "showTabsInTitlebar" : true,
    "profiles":
    [
        {
            "guid": "__default__",
            "useAcrylic": true,
            "acrylicOpacity": 0.1,
            "fontFace": "Cascadia Code",
            "fontSize": 10
        },
        {
            "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "name": "Windows PowerShell",
            "commandline": "powershell.exe",
            "hidden": false
        },
        {
            "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
            "name": "cmd",
            "commandline": "cmd.exe",
            "hidden": false
        }
    ],
    "schemes": [],
    "keybindings": []
}

优势

  • 同样把默认设置封装在一个对象里;不过因为该对象可以位于列表任意位置,清晰度略逊于方案一。
  • 默认 Profile 与其他 Profile 归入同一个 profiles 列表,结构上“都在一个地方”。

顾虑

  • 神秘的 __default__ GUID:认定该 Profile 特殊的唯一手段是给它一个常量字符串,而这个字符串并非合法 GUID,本身就值得怀疑。
  • 反直觉:靠添加一个带神秘 guid 的 Profile 来充当全局默认,这一机制没有任何文档之外的提示能告诉用户“加这个魔法配置块就能影响所有 Profile”。
  • 一个 Profile 凭什么作用于其他所有 Profile? 列表中某一项影响其余各项,这在直觉上很难自洽。

方案三:把 profiles 改为含 listdefaults 的对象(最终采纳)

第三个提案改变了 profiles 的类型:从数组变为对象,对象内含 defaults(默认配置块)和 list(Profile 数组)两个键:

{
    "$schema": "https://aka.ms/terminal-profiles-schema",
    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
    "requestedTheme" : "dark",
    "showTabsInTitlebar" : true,
    "profiles":
    {
        "defaults": {
            "useAcrylic": true,
            "acrylicOpacity": 0.1,
            "fontFace": "Cascadia Code",
            "fontSize": 10
        },
        "list":[
            {
                "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
                "name": "Windows PowerShell",
                "commandline": "powershell.exe",
                "hidden": false
            },
            {
                "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
                "name": "cmd",
                "commandline": "cmd.exe",
                "hidden": false
            }
        ]
    },
    "schemes": [],
    "keybindings": []
}

优势

  • 与 Profile 归组:默认值与 Profile 列表同处一个 profiles 对象之下,语义关系一目了然。
  • 向后兼容:文档特别指出,借助 Jsoncpp 可以在运行时判断 profiles数组还是对象——是数组就回退到原有行为(必然没有 defaults),是对象就进入新结构取 defaultslist。用户既有设置文件因此不会被破坏。

顾虑

  • Schema 变更幅度不小profiles 从 Profile 对象列表变成了内嵌列表的对象。但如上所述可以通过运行时类型判别平滑升级,因此文档认为“并非重大问题”。
  • 所有 Profile 多一层缩进:四层缩进让一些人不太舒服——这是纯粹的体验层面顾虑。

方案四:Profile 中的 inheritFrom 继承机制

第四个提案引入了通用的配置继承:给 Profile 增加 inheritFrom 属性,指向父 Profile 的 GUID,支持多层继承链:

{
    "$schema": "https://aka.ms/terminal-profiles-schema",
    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
    "requestedTheme" : "dark",
    "showTabsInTitlebar" : true,
    "profiles":
    [
        {
            "guid": "{11111111-1111-1111-1111-111111111111}",
            "hidden": true,
            "useAcrylic": true,
            "acrylicOpacity": 0.1,
            "fontFace": "Cascadia Code",
            "fontSize": 10
        },
        {
            "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "inheritFrom": "{11111111-1111-1111-1111-111111111111}",
            "name": "Windows PowerShell",
            "commandline": "powershell.exe",
            "hidden": false
        },
        {
            "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
            "inheritFrom": "{11111111-1111-1111-1111-111111111111}",
            "name": "cmd",
            "commandline": "cmd.exe",
            "hidden": false
        },
        {
            "guid": "{0caa0dad-ffff-5f56-a8ff-afceeeaa6101}",
            "inheritFrom": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
            "name": "This is another CMD",
            "commandline": "cmd.exe /c myCoolScript.bat",
            "hidden": false
        }
    ],
    "schemes": [],
    "keybindings": []
}

优势

  • 无需大改既有设置模型:只是给 Profile 加一个新属性,文件结构基本不变。
  • 属性名唯一inheritFrom 相对已有键名非常独特,不易冲突。
  • 表达力强:允许任意多层配置分组。用户可以把公共设置拆成多套“准默认”分组,而不被单一的“default”Profile 绑死,例如:一套给所有 WSL Profile(startingDirectory 设为 ~fontFace 设为 "Ubuntu Mono"),一套给所有 PowerShell Profile,等等。

顾虑

  • GUID 对人不友好inheritFrom 只能引用 GUID 才能保证唯一标识,而示例中手写的 {11111111-1111-1111-1111-111111111111} 尚且易读,继承自“真实”GUID 的 Profile 时(如“This is another CMD”继承自 “cmd”),其 inheritFrom 值一眼看上去完全不传达“cmd”的含义。
  • 必须防循环引用:多层叠加时要确保继承链不成环,实现上更棘手。
  • 与设置 UI 如何协同:用户在 UI 中编辑某个继承 Profile 时,改动应该只写到最上层 Profile 吗?UI 又该如何向用户传达“此 Profile 正在从其他 Profile 继承设置”?
  • 心智负担更重:用户需要自己在脑中构建一棵继承树,才能理解某个 Profile 的最终设置从何而来。

结论:采纳方案三,方案四进入特性积压

规格文档给出的最终结论是:团队选择了方案 3,主要卖点为——

  • 把新的“默认 Profile 设置”与其余 Profile 设置归组在一起;
  • 虽然是 Schema 变更,但不是破坏性变更(运行时类型判别保证旧文件可用);
  • 查看设置时容易理解两者的关系。

同时团队表示方案四的理念也不错,但对这样一个相对简单的需求而言过于“重”了,于是将其放入终端特性积压清单(issue #3818)。文档的 Resources 部分还列出了三个关联事项:原始 issue #2325(Default Profile for Common Profile Settings)、原始 PR #3369(Add support for "User Default" settings)以及 #3818(Add support for inheriting and overriding another profile's settings)。

源码印证:defaults / list 结构在当前实现中的落地

以上设计并非纸面方案,当前仓库的终端设置模型(src/cascadia/TerminalSettingsModel/)已经按方案 3 落地,并且向后兼容逻辑与文档描述一致。

关键解析入口:_parseJson 的数组/对象判别

CascadiaSettingsSerialization.cpp 中,两个键名常量正是文档方案 3 的产物:

static constexpr std::string_view DefaultSettingsKey{ "defaults" };
static constexpr std::string_view ProfilesListKey{ "list" };

CascadiaSettingsSerialization.cpp#L38-L39。而 _parseJson 的实现精确对应了文档中“如果是数组就回退到旧行为”的兼容策略:

const auto& profilesObject = _getJSONValue(root, ProfilesKey);
const auto& profileDefaults = _getJSONValue(profilesObject, DefaultSettingsKey);
const auto& profilesList = profilesObject.isArray() ? profilesObject : _getJSONValue(profilesObject, ProfilesListKey);

CascadiaSettingsSerialization.cpp#L1041-L1044:当 profiles 是数组(isArray() 为真)时,整个对象被直接当作 Profile 列表,defaults 取不到即为空;当它是对象时才读取 list 键。这与规格文档 Proposal 3 中 “With Jsoncpp, we can determine at runtime if an object is an array or an object” 的表述完全吻合。

defaults 被解析为“基础层 Profile”

解析出的 defaults 块并不只是简单合并,而是被构造成一个基础层 Profile:

settings.baseLayerProfile = Profile::FromJson(json.profileDefaults);

CascadiaSettingsSerialization.cpp#L873。也就是说,profiles.defaults 中的每个属性都走与具体 Profile 相同的反序列化路径(字体、颜色、外观等全部有效),再作为所有 Profile 的“基座”参与继承。

官方 Schema 与实际设置文件

  • 官方 JSON Schema profiles.schema.json 中,profiles 被定义为同时包含 listdefaults 两个属性的对象(见该文件第 3258、3261 行附近),确认了方案 3 的结构已成为正式契约。
  • 仓库自带的用户默认设置 userDefaults.json 本身就采用新结构:
"profiles":
{
    "list":
    [
        {
            "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "name": "Windows PowerShell",
            "commandline": "%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
            "hidden": false
        },
        ...
    ]
}
  • 系统默认 defaults.json 中的 "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}"(Windows PowerShell 的 GUID)与规格文档所有示例中使用的 GUID 一致,便于对照阅读。

继承机制在模型层的一般化

defaults 块之所以能“作用于所有 Profile”,底层依赖的是设置模型中更通用的父/子继承体系。IInheritable.h 定义了让设置对象从父对象继承值的接口,其核心属性宏按“用户显式设置的值 → 继承值 → 系统默认值”的优先级回退;Profile.h 的文件头注释还直接画出了 Profile 继承树的形态(Profile 的 defaults 层在树根,各 Profile 作为其子节点)。此外 CascadiaSettingsSerialization.cpp#L1003 附近 “Merge profiles, color schemes, and globals into the user settings (aka inheritance)” 的代码段,展示了内置/动态 Profile 如何以父节点身份挂到用户 Profile 上——方案 4 所描述的“多层继承”在实现层已有基础设施,只是对用户暴露的 JSON 层面当时只采纳了方案 3 的单一 defaults 层。

实战使用要点与适用前提

结合文档结论与当前源码,实际使用上有几点值得注意:

  1. 在终端设置 JSON 中写公共默认值:把希望统一生效的 Profile 级属性(如 fontFacefontSizeuseAcrylicacrylicOpacitystartingDirectory 等)写入 profiles.defaults 对象;各具体 Profile 中显式写出的属性会覆盖默认值,未写出的属性则继承默认值。
  2. profiles 同时兼容两种形态:从实现看,写成数组(旧格式)仍被支持,此时没有默认值可用;写成 { "defaults": {...}, "list": [...] } 对象(新格式)才能启用本特性。迁移时旧文件不会失效,这正对应文档中 “gracefully upgrade” 的设计承诺。
  3. defaults 中不要写 guid:它是配置块而非真实 Profile,示例中所有示例均未为其指定 guiddefaultProfile 仍应指向 list 中某个真实 Profile 的 GUID。
  4. 继承的局限:当前对用户暴露的是单层“defaults → profile”继承;方案 4 讨论过的多层 Profile 间继承(inheritFrom)在当时被明确推迟到 backlog(#3818),不要假设该能力随本文档落地。
  5. 适用前提:以上内容基于本仓库(Windows 新一代终端及其原始控制台宿主共存的代码库)中 src/cascadia 设置模型的当前实现与 doc/cascadia 下的官方 Schema;defaults/list 结构是终端 JSON 设置文件的契约,与 conhost(原控制台宿主)的注册表配置路径无关。

相关文档与延伸阅读

  • 设计规格正文:[#2325 - Default Profile Settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#2325 - Default Profile Settings.md)
  • 设置模型总体设计:[#885 - Terminal Settings Model.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#885 - Terminal Settings Model/#885 - Terminal Settings Model.md)
  • 级联默认设置规格(defaults.json 分层的后续演进):[#754 - Cascading Default Settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#754 - Cascading Default Settings.md)
  • 外观配置对象(Profile 级外观属性的结构化扩展):[#3062 - Appearance configuration object for profiles.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#3062 - Appearance configuration object for profiles.md)
  • 官方设置 Schema:profiles.schema.json
  • 设置模型实现:CascadiaSettingsSerialization.cppIInheritable.hProfile.h
  • 默认配置文件:defaults.jsonuserDefaults.json
登录后查看全文
热门项目推荐
相关项目推荐