Windows Terminal 设置 UI 设计解析:导航结构、外观预览窗口与级联继承在源码中的落地
本文围绕 [设计文档](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/design.md)(issue #1564 的 Settings UI Design)展开,讲清楚 Windows Terminal 设置界面的页面导航布局、各页面的完整设置项清单、带实时预览窗口的 Appearance 页设计以及键盘绑定编辑弹窗形态,并结合仓库中 TerminalSettingsEditor 与 TerminalSettingsModel 的实际源码,印证这些设计如何演化为今天可以运行的设置 UI——读完后你既能理解设计稿中每个页面与控件的意图,也能定位到实现它的 XAML、导航标签与继承机制代码。
一、设计文档的定位:从 issue #1564 到三份配套规格
[design.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/design.md) 的文档头元数据表明,它由 Kayla Cinnamon 于 2020-07-13 创建、2020-08-11 最后更新,对应 issue #1564。文档摘要明确了两点:
- 它描述设置 UI 每个页面的布局,并给出设计稿(mockup)展示 UI 的外观;
- 设计稿只用于示意外观,其中的布局与命名可能与最终实现不同,该文档被视为最终裁决("This doc should be considered the final say")。
同一目录下还有两份配套文档,与 design.md 互为补充:
- [spec.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/spec.md):定义设置 UI 的基本功能——如何禁用 UI、导航项、启动方式与设置编辑保存机制;
- [cascading-settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/cascading-settings.md):探讨级联设置(cascading settings)与
profiles.defaults如何在设置 UI 中表示。
值得注意的是,当时 Windows Terminal 的默认设置体验是用文本编辑器打开 settings.json,设置 UI 是全新引入的界面,因此设计文档同时给出了导航方案对比(更细粒度的分类导航 vs 与 JSON 结构对齐的导航),并在 [spec.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/spec.md) 中记录了备选方案(新窗口启动、自动保存等)及各自的取舍。
二、顶层导航设计:General / Appearance / Profiles / Keyboard
design.md 给出的左侧导航栏顶层结构如下(带星号的页面在对应功能实现后才加入):
- General
- Startup
- Interaction
- Rendering
- Appearance
- Global
- Color schemes
- Themes*
- Profiles
- Defaults
- Enumerate profiles
- Add new
- Keyboard
- Mouse*
- Command Palette*
- Marketplace*
在 [spec.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/spec.md) 中,还记录了另一套被否决的导航方案——"与 JSON 对齐":顶层只有 Globals、Profiles、Color schemes、Bindings 四项(Bindings 下挂 key bindings、mouse bindings 与 command palette)。最终产品选择了更细分、更易消化的分类导航,理由是"与其他终端更一致"。
对照源码:今天的导航项与导航标签
从 MainPage.xaml 的 NavigationView.MenuItems 可以看到,现行导航项已演进为平铺的页面列表:Launch(对应原 General-Startup)、Interaction、Appearance、Color Schemes、Rendering、Compatibility、Actions(带 New 徽标)、New Tab Menu、Extensions,加上 Profiles 分组头与 Base Layer(x:Uid="Nav_ProfileDefaults",即级联设置文档中把 "Global" 页改名后的 "Base layer")。导航标签常量集中定义在 NavConstants.h,其中 til::static_map 把每个 NavigationTag 映射到 Segoe MDL2 Assets 图标字形,例如 launchTag → \xE7B5、actionsTag → \xE765(Keyboard Classic)。
spec.md 中"导航菜单底部放一个 'Open the JSON file' 按钮"的设计也已落地:MainPage.xaml 的 FooterMenuItems 中有 OpenJsonNavItem(Tag="OpenJson_Nav"),用户随时可以从 UI 回到原始 JSON 文件编辑。
三、Appearance 页设计:内嵌 TerminalControl 的实时预览窗口
design.md 指出 Profile appearance 页需要特殊设计,因为它内嵌了一个 TerminalControl 预览窗口,用于实时预览外观变更。该预览窗口出现在两个页面:
- Appearance - Color Schemes
- Profiles - Appearance
源码印证:预览窗口不启动真实进程
PreviewConnection.cpp 实现了这个预览窗口背后的数据源:它实现了一个 TerminalConnection,但 WriteInput/Resize/Close 全部为空操作,Start() 只通过 TerminalOutput.raise(...) 向渲染层写出一段固定的示例文本——包括硬重置序列(RIS)、Windows Terminal 标题、一段仿 git diff -w 的彩色输出(红 - Windows Console、绿 + Windows Terminal!、灰 Write-Host "🛃!")。它还根据字体是否支持 powerline 字形在 PromptTextPlain(C:\> )与 PromptTextPowerline(\x1b[49;34m\xe0b6… 等 ANSI 序列拼出的 C:\ 提示符)之间切换并重新发送。
从源码结构看,预览窗口与真实终端共用同一个 TerminalControl 渲染管线,只是数据源换成 PreviewConnection:用户每改一项颜色、字体或 acrylic 参数,渲染层就会用新的设置重绘这段固定样例,从而在点击保存前看到外观效果——这正是 spec.md 中"在写回 settings.json 之前预览变更"这一决策的实现路径。
四、Keyboard 页设计:绑定列表与动态生长的编辑弹窗
design.md 对键盘页的描述包含三个要点:
- 页面列出所有已启用的按键绑定,并提供添加与删除入口;
- 悬停某一项时出现 Edit 与 Delete 按钮;
- 点击 Edit 弹出模态框:对于无参数/无 actions 的命令,模态框只显示命令与按键输入区;若命令带有额外参数或 actions,模态框会随参数/actions 的添加动态增高。设计稿同时注明,希望未来的输入框能"监听"按键组合,即界面上增加一个 "listen" 按钮。
今天该页面已演进为 Actions 页:Actions.xaml 与 EditAction 承担"绑定列表 + 编辑弹窗"的职责,而 "listen" 构想则由 KeyChordListener 组件落地——它暴露一个 Keys 依赖属性(DEPENDENCY_PROPERTY(Control::KeyChord, Keys))并在 KeyChordTextBox_KeyDown 中捕获按键事件把组合键写入 KeyChord,即"点击输入框后直接按键即可录入组合"。
五、完整设置清单:八个页面的控件布局
design.md 的核心内容是一张覆盖全部设置项的布局表。标题行与 UI 左侧的导航视图对齐,列中加粗的标题(如 General、Appearance、Advanced)对应页面顶部的 pivot 导航。以下完整继承原文档表格:
| General - Startup | General - Interaction | General - Rendering | Appearance - Global | Appearance - Color Schemes | Profiles - Global | Profiles - Enumerate profiles | Profiles - Add new |
|---|---|---|---|---|---|---|---|
| Default profile (dropdown) | Copy after selection is made (checkbox) | Software rendering (checkbox) | Theme (radio) | Name (text box) | General | General | General |
| Launch on startup (checkbox) | Copy formatting (checkbox) | Screen redrawing (checkbox) | Show/Hide the title bar (checkbox) | Cursor color (color picker) | Command line (text box) | Scrollbar visibility (radio) | Scrollbar visibility (radio) |
| Launch size (radio) | Word delimiters (text box) | Show terminal title in title bar (checkbox) | Selection background (color picker) | Starting directory (browse button) | Command line (browse button) | Command line (browse button) | |
| Launch position (text box) | Window resize behavior (checkbox) | Always show tabs (checkbox) | Background (color picker) | Icon (browse button) | Starting directory (browse button) | Starting directory (browse button) | |
| Columns on first launch (number picker) | Tab width mode (radio) | Foreground (color picker) | Tab title (text box) | Name (text box) | Name (text box) | ||
| Rows on first launch (number picker) | Hide close all tabs popup (checkbox) | Black (color picker) | Scrollbar visibility (radio) | Icon (browse button) | Icon (browse button) | ||
| Automatically create new profiles when new shells are installed (checkbox) | Blue (color picker) | Appearance | Tab title (text box) | Tab title (text box) | |||
| Cyan (color picker) | Font face (text box) | Appearance | Appearance | ||||
| Green (color picker) | Font size (number picker) | Retro terminal effects (checkbox) | Retro terminal effects (checkbox) | ||||
| Purple (color picker) | Font weight (dropdown) | Font face (text box) | Font face (text box) | ||||
| Red (color picker) | Padding (text box) | Font size (number picker) | Font size (number picker) | ||||
| White (color picker) | Cursor shape (radio) | Font weight (dropdown) | Font weight (dropdown) | ||||
| Yellow (color picker) | Cursor color (color picker) | Padding (text box) | Padding (text box) | ||||
| Bright black (color picker) | Cursor height (number picker) | Cursor shape (radio) | Cursor shape (radio) | ||||
| Bright blue (color picker) | Color scheme (dropdown) | Cursor color (color picker) | Cursor color (color picker) | ||||
| Bright cyan (color picker) | Foreground color (color picker) | Cursor height (number picker) | Cursor height (number picker) | ||||
| Bright green (color picker) | Background color (color picker) | Color scheme (dropdown) | Color scheme (dropdown) | ||||
| Bright purple (color picker) | Selection background color (color picker) | Foreground color (color picker) | Foreground color (color picker) | ||||
| Bright red (color picker) | Enable acrylic (checkbox) | Background color (color picker) | Background color (color picker) | ||||
| Bright white (color picker) | Acrylic opacity (number picker) | Selection background color (color picker) | Selection background color (color picker) | ||||
| Bright yellow (color picker) | Background image (browse button) | Enable acrylic (checkbox) | Enable acrylic (checkbox) | ||||
| Background image stretch mode (radio) | Acrylic opacity (number picker) | Acrylic opacity (number picker) | |||||
| Background image alignment (dropdown) | Background image (browse button) | Background image (browse button) | |||||
| Background image opacity (number picker) | Background image stretch mode (radio) | Background image stretch mode (radio) | |||||
| Retro terminal effects (checkbox) | Background image alignment (dropdown) | Background image alignment (dropdown) | |||||
| Advanced | Background image opacity (number picker) | Background image opacity (number picker) | |||||
| Hide profile from dropdown (checkbox) | Advanced | Advanced | |||||
| Suppress title changes (checkbox) | GUID (text box) | GUID (text box) | |||||
| Antialiasing text (radio) | Hide profile from dropdown (checkbox) | Hide profile from dropdown (checkbox) | |||||
| AltGr aliasing (checkbox) | Suppress title changes (checkbox) | Suppress title changes (checkbox) | |||||
| Scroll to input when typing (checkbox) | Antialiasing text (radio) | Antialiasing text (radio) | |||||
| History size (number picker) | AltGr aliasing (checkbox) | AltGr aliasing (checkbox) | |||||
| How the profile closes (radio) | Scroll to input when typing (checkbox) | Scroll to input when typing (checkbox) | |||||
| History size (number picker) | History size (number picker) | ||||||
| How the profile closes (radio) | How the profile closes (radio) |
表格与真实默认值 JSON 的对应关系
表中每一项都对应 defaults.json 中的一个 JSON 键。该文件是系统级默认值层(设计文档中"system set value"的来源),节选可验证上述表格的取值形态:
- Startup 组:
initialCols: 120、initialRows: 30、launchMode: "default"对应 "Columns/Rows on first launch" 与 "Launch size" 控件; - Interaction 组:
copyOnSelect: false、copyFormatting: true、wordDelimiters: " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502"对应 "Copy after selection is made / Copy formatting / Word delimiters" 三个控件; - Appearance - Global:
alwaysShowTabs: true、showTerminalTitleInTitlebar: true、tabWidthMode: "equal"、theme: "dark"对应表中 "Always show tabs / Show terminal title in title bar / Tab width mode / Theme" 控件; - Profiles 层:
historySize: 9001、closeOnExit: "automatic"、antialiasingMode: "grayscale"、altGrAliasing: true、snapOnInput: true等对应 Advanced pivot 下的 History size、How the profile closes、Antialiasing text、AltGr aliasing、Scroll to input when typing 控件;useAcrylic: false对应 Enable acrylic。
这验证了 [cascading-settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/cascading-settings.md) 中所述的三级取值链:用户 settings.json 值 → defaults.json 值 → 系统内建值。
六、保存机制:Save 按钮、未保存提示与 Portable 模式
spec.md 对编辑保存的决策是"实现一个 Save 按钮":
- 用户只有点击 Save 后,变更才写回
settings.json——与今天用文本编辑器改 JSON 并保存的行为对齐; - 被否掉的备选方案是"边改边自动保存",实时生效但失去了与 JSON 工作流的对等性。
当前实现与该决策一致,且功能更多:MainPage.xaml 底部条包含 SaveButton(AccentButtonStyle 强调按钮)与 ResetButton,左侧有一个默认 Collapsed 的 Settings_UnsavedSettingsWarning 文本(Goldenrod 颜色)用于提示存在未保存变更;当 CascadiaSettings.IsPortableMode 为真时还会显示 Portable 模式说明与链接。保存/重置的实际处理逻辑在 MainPage.cpp 的 SaveButton_Click / ResetButton_Click 中。
spec.md 同时要求整个 UI 通过完整的无障碍测试:所有条目必须可被屏幕阅读器和键盘访问,并全量本地化。从 TerminalSettingsEditor/Resources 下覆盖 de-DE、zh-CN、ja-JP 等十余种语言的 Resources.resw 目录结构看,本地化要求已被落实。
七、级联设置在 UI 中的表示:Base layer、重置与跨 Profile 复制
[cascading-settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/cascading-settings.md)(Carlos Zamora 与 Kayla Cinnamon,2020-11-10)探讨的是:既然每个设置的值可能来自 profile 自身、profiles.defaults 或 defaults.json 多层声明,设置 UI 应如何表达"覆盖/继承"关系。文档给出了一组将组合使用的 UI 提案:
- 控件下方文字标注:把 Profiles 下的 "Global" 页改名为 "Base layer";覆盖了 base layer 的设置,控件下方显示 "Overrides Base layer." 文字,标题旁提供一个带 "Reset" 提示的重置按钮;
- Add New → Duplicate Profile:新增 Profile 入口改为一个选择页,用户可以在"复制某个已有 profile"与"从默认设置新建"之间单选,随后进入的 Profile 页会按所选来源预填字段;
- Reset Profile 按钮:在 Profile 页的 Advanced pivot 底部提供 "Reset to default settings",清除该 profile 对象内的自定义设置,优先级回落到
profile.defaults然后 defaults.json; - "Apply to all profiles":每个 Profile 的 Advanced 页提供 "Copy settings to..." 按钮,弹出对话框以树视图列出全部 profile 设置项供勾选,底部以复选框列出所有 profile 作为复制目标。
文档还为每个提案对比了同类终端的做法(ConEmu/Cmder 的 Clone profile、iTerm2 的 Bulk Copy from Selected Profile 等),并记录了两个被否决的方案及其理由:
<inherit>下拉项:每个设置变成可编辑 ComboBox、附带 inherit 选项。优点是界面不杂乱,缺点是每个设置都是下拉框,且颜色选择器在该模型下无法自洽;- 锁按钮:锁住 = 继承自 Global 且控件禁用。缺点是语义易误解("锁住"直觉上表示"固定在本 profile",与当前设计恰好相反)。
源码印证:Has/Clear/OverrideSource 三件套
cascading-settings.md 指出 XAML 层会为每个设置引入一个 ContentControl 包装控件,并利用 TerminalSettingsModel 提供的四件套 API:
// Note: String and "Name" are replaced for each setting
bool HasName();
void ClearName();
String Name();
void Name(String val);
这套接口在 IInheritable.h 中由宏体系完整实现:
INHERITABLE_SETTING宏为每个设置生成Has<NAME>()(判断用户是否显式设置过值)、<NAME>OverrideSource()(返回提供当前解析值的那一层对象)、Clear<NAME>()(清回继承态)与 getter/setter;- getter 的解析注释直接写着 fallback 顺序:
user set value --> inherited value --> system set value——与文档摘要中"settings.json 值 → defaults.json 值 → 系统值"的级联链一一对应; - 每个设置存储为
std::optional,nullopt表示"必须向父层继承";_parents是一个std::vector<com_ptr<T>>,AddLeastImportantParent/AddMostImportantParent维护父子优先级,<NAME>OverrideSource()会沿父链回溯找到第一个显式设置该值的祖先; - 文档特别提到
INHERITABLE_NULLABLE_SETTING用于Profile.Foreground这类"null 本身是合法值"的可空设置,用双层 optional(NullableSetting<T> = std::optional<std::optional<T>>)区分"未设置需继承"与"显式清空为 null"两种状态。
正是 OverrideSource() 让 UI 能精确回答"这个值到底来自哪一层",从而驱动 "Overrides Base layer." 标注与每行重置按钮——设计文档中抽象的 API 需求在这里有了逐行对应的实现。
八、能力与兼容性:文档中的边界声明
spec.md 的 Capabilities 一节明确了该功能的能力边界,写作与评审设置 UI 相关代码时值得参照:
- 无障碍:全新 UI 元素,必须完成屏幕阅读器与键盘全路径测试,且全量本地化;
- 安全 / 可靠性 / 性能功耗:均不受影响;
- 兼容性:默认体验从"文本编辑器打开 JSON"变为"打开设置 UI",且该行为可通过修改
openSettings键绑定加settingsUI选项回退——对应今天的 Actions 页中 openSettings 的settingsUI参数; - 未来考量:
hidden属性需要特殊处理(理想情况下所有 profile 无论hidden与否都应出现在设置中)、需要撤销功能、Marketplace 实现后加入顶层导航、导航结构随功能增长持续调整。
cascading-settings.md 另指出一个兼容性取舍:设置 UI 只提供 JSON 的"部分对等"(partial parity)是有意的——它是面向普通用户的简化工具,若为追求全量对等而堆满选项,会牺牲设置 UI 的简洁性这一核心价值。
九、延伸阅读路径
围绕本文主题,仓库内可按以下路径继续深入:
- 设计稿三件套:[design.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/design.md)、[spec.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/spec.md)、[cascading-settings.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1564 - Settings UI/cascading-settings.md);
- UI 实现:MainPage.xaml(导航、搜索框、Save/Reset 底栏)、NavConstants.h(导航标签与图标映射)、PreviewConnection.cpp(外观预览数据源)、KeyChordListener.h(组合键录入)、ActionEntry.cpp 所在的 Actions 模型;
- 设置模型与继承机制:IInheritable.h(继承链与可清空设置宏)、Profile.h、defaults.json(系统默认值层,文件头注明为自动生成,修改会被忽略);
- 更宏观的模型背景可参阅 [Terminal Settings Model 规格](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) 与 TerminalSettings-spec.md。
需要说明适用前提:design.md 成文于 2020 年,文中 "Launch on startup"、"Launch position" 等 Startup 组控件与现行 Launch.xaml 的项集合已有差异(现行版本新增了 startupActions、alwaysOnTop 等,defaults.json 中可见);但导航分层思路、Appearance 预览窗口、Save 保存模型与级联继承表达这四大设计决策,在今天的源码中仍然成立。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00