Windows Terminal Suggestions UI 设计详解:在终端光标处构建类 Intellisense 的统一建议菜单
2026/9/15 17:38:24 网站建设 项目流程

Windows Terminal Suggestions UI 设计详解:在终端光标处构建类 Intellisense 的统一建议菜单

【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal

本文基于 OpenConsole 仓库中#1595 - Suggestions UI设计规格文档,系统讲解 Windows Terminal「建议 UI(Suggestions UI)」的设计动机、建议来源(commandHistory / directoryHistory / tasks / local 等)、Palette 与 Menu 两种交互模式的取舍、suggestions动作的 JSON 配置参数(sourceuseCommandlinenesting)及其 JSON Schema 定义,并结合仓库中 SuggestionsControl 的实际实现源码,印证该 UI 如何以光标为锚点定位、如何从历史命令构建建议列表、以及被否决的异步建议方案背后的原因。读完本文,你可以完整理解该功能的规格脉络,并能在自己的settings.json中正确配置各类建议来源。

一、什么是 Suggestions UI

Windows Terminal 中存在多类场景,希望在用户正在输入的终端上下文中展示可操作的 UI,类似于 Visual Studio 中的 Intellisense:菜单出现在用户键入的位置,并根据当前上下文即时提供内容。Suggestions UI就是 Terminal 中这套统一的、临时性(ephemeral)的 UI 控件,用于展示来自不同来源不同类型的动作建议。

它能呈现的建议包括:

  • 用户近期在本终端执行过的命令(由 shell integration 提供数据);
  • 近期访问的目录(同样由 shell integration 提供);
  • shell 自身提供的补全(例如 PowerShell 的 tab 补全);
  • 用户设置中保存的 Tasks(本质上是sendInput动作);
  • Buffer Completions:一种基于缓冲区中已有单词的「简单」自动补全;
  • 以及更多由扩展提供的来源。

所有这些场景的共同点都在于:在终端控件内部的文本插入点处呈现一个菜单是合理的。其设计灵感来自各类应用中的 Intellisense 式体验(Visual Studio、VsCode、PowerShell、vim、JetBrains IDE 等),并最终在与 PowerShell 和 VsCode 团队的协作中收敛为统一的 UI 设计。

用户故事(按规模分级)

规格文档用 Crawl / Walk / Run / Sprint 四个量级描述了功能边界:

级别描述
🐣 Crawl用户可以用 shell integration 驱动的「近期命令」调出 Suggestions UI
🐣 Crawl用户可以用 shell integration 驱动的「近期目录」调出 Suggestions UI
🚶 Walk用户可以用设置中的 tasks 调出 Suggestions UI
🚶 WalkCLI 应用可以通过一条新的 VT 序列调出 Suggestions UI
🚶 WalkSuggestions UI 可以用用户当前已键入的命令行作为过滤条件打开
🚶 Walk近期命令与目录保存在state.json中,跨会话保留
🏃‍♂️ Run建议可以携带描述信息,在 UI 中或以附加形式展示
🏃‍♂️ RunSuggestions UI 可以不带任何嵌套结构打开
🏃‍♂️ RunSuggestions UI 可以按建议的source(来源)分组嵌套打开
🚀 Sprint扩展可以为 Suggestions UI 提供建议来源
🚀 SprintSuggestions UI 支持「inline」模式,仅内联显示第一条建议的文本

其核心诉求之一还来自可访问性:PowerShell 当前的自动补全菜单与 UIA(屏幕阅读器)客户端的交互体验不佳,微软 Visual Studio 团队也一直希望终端能提供更丰富的上下文描述。Suggestions UI 借助 WinUI 的可访问性设计,让终端能以 UI 元素的形式向屏幕阅读器传达「当前选中项」等 shell 侧概念,从而提升而非降低命令行建议的可访问性。

二、建议来源(Completion Sources)与配置动作

Suggestions UI 支持来自多种「来源」的建议。规格文档给出的动作配置示例如下:

{ "command": { "action":"suggestions", "source": "commandHistory" } }, { "command": { "action":"suggestions", "source": "directoryHistory" } }, { "command": { "action":"suggestions", "source": "tasks" } }, { "command": { "action":"suggestions", "source": "local" } }, { "command": { "action":"suggestions", "source": ["local", "tasks", "commandHistory"] } }, { "command": { "action":"suggestions", "source": "Microsoft.Terminal.Extensions.BufferComplete" } }

每条suggestions动作都会用不同的动作集合打开 Suggestions UI。各内置来源的语义为:

来源行为
commandHistory使用本会话中通过 shell integration 识别出的近期命令。若用户未配置 shell integration 序列,此来源无法返回任何建议
directoryHistory填充一系列cd {path}命令,路径经由 shell integration 获得,按 MRU(最近使用)顺序排列
tasks填充用户设置文件中所有sendInput动作。保留原有的命令结构——例如嵌套在 "git" 命令下的sendInput动作,在该视图中仍会嵌套在 "git" 条目之下
local填充位于当前工作目录下.wt.json文件中的 tasks
扩展提供的来源Microsoft.Terminal.Extensions.BufferComplete,展示扩展如何以「来源」的形式接入建议体系

这些来源各自构建一组Command,其中主要是sendInput动作;随后这些Command被加载进 Suggestions UI 控件,并在文本光标处打开。以commandHistory为例:TerminalPage 会向当前活动的 TermControl 查询其近期命令列表;如果该列表已知(经由 shell integration),TerminalPage 就用它构建一组sendInput动作并送入建议 UI。

值得注意的是,shell 驱动的自动补全并不在上面的内置来源之列——这类补全不是终端自己能发起的,必须由 shell 主动触发。

在仓库源码中可以印证这一设计的落地:

  • TerminalSettingsSerializationHelpers.h 中定义了来源字符串到内部类型的映射:"commandHistory"对应ValueType::CommandHistory"directoryHistory"对应ValueType::DirectoryHistory,即规格中描述的内置枚举来源在反序列化层的实现入口;
  • Command.idl 声明了静态工厂方法HistoryToCommands(commandHistory, commandline, directories, iconPath),参数中的commandline对应已键入命令行(即useCommandline的过滤起点),directories布尔位则区分commandHistorydirectoryHistory两种视图——这正是规格文档中「用命令历史构建 sendInput 动作」这一步的源码实现;
  • TerminalPage.cpp 中可以看到_OpenSuggestions的调用链,其中 shell 驱动补全场景会以SuggestionsMode::Menu模式打开建议控件。

三、Palette 与 Menu:两种交互形态

根据 Suggestions UI 的调用方式,决定是否显示一个过滤用的文本框:

  • Palette(调色盘)模式:带有过滤文本框,对建议内容做模糊搜索过滤,类似 Command Palette 的搜索体验。Terminal 自身驱动的建议(如commandHistory)采用这种形态。
  • Menu(菜单)模式:不显示过滤文本框,只用上/下方向键导航、回车/Tab 选择。shell 驱动的补全应采用这种形态——此时用户主要在和 shell 交互,而不是和 Terminal 交互。

规格文档还详细讨论了键位行为的权衡:

  • PowerShell 的 inline 建议用「右方向键」表示「采用该建议」,与「对当前输入做 tab 展开」区分开;这个行为应当保留;
  • 在 Menu 模式下,焦点仍在终端中,用户会期望 Tab 触发 tab 补全;在 Palette 模式下,焦点已离开终端进入建议 UI,Tab 应专注于焦点导航;
  • 规格作者的原型中,Menu 模式接受右方向键、Tab 和回车三者中的任何一个作为「接受补全」,其余任意按键关闭 UI;但 Palette 模式下 Tab 需要用于焦点导航,不能照搬同样的规则。目标是让 Suggestions UI、Terminal 的 Command Palette、VsCode 的 Intellisense 在体验上保持一定的一致性。

这一设计在实现中得到了直接印证:SuggestionsControl.idl 中定义的枚举正是规格中两种形态(以及尚未启用的第三形态):

enum SuggestionsMode { Palette = 0, Menu, // Inline, // 规格中的 "Inline mode" 未来构想,目前仍被注释掉 }; enum SuggestionsDirection { TopDown, BottomUp }

其中被注释掉的Inline对应规格「未来考量」一节中「只显示第一条建议文本的内联模式」的设想,说明实现严格跟随了规格的分期规划。

四、关键配置参数:useCommandline 与 nesting

useCommandline:用已键入命令行预填过滤

考虑一个场景:用户在 shell 中敲了git c并启用了 shell integration,希望打开 Suggestions UI 过滤到近期历史,但从已经输入的部分开始。为此规格引入了一个布尔属性:

  • "useCommandline": bool默认true
    • true:用户当前已键入的命令行将预填 Suggestions UI 的过滤框。要求用户在 shell 配置中启用了 shell integration;
    • false:无论用户输入了什么,过滤框从空开始。

对应的动作为:

{ "command": { "action":"suggestions", "source": "commandHistory", "useCommandline": true } }

这样用户键入git c后调用 Suggestions UI,可以立即搜索以git c开头的近期命令。useCommandline: false的主要用途配合"nesting": "source":当在过滤「["Tasks...", "Recent commands...", "Recent directories...", "Docker...", "Git..."]」这样的来源列表时,用 "git c" 去预过滤几乎没有价值。

默认动作

规格建议默认加入以下四个动作,分别对应:

{ "command": { "action":"suggestions", "source": "commandHistory", "useCommandline": true } }, { "command": { "action":"suggestions", "source": "directoryHistory" } }, { "command": { "action":"suggestions", "source": ["local", "tasks", "commandHistory"], "useCommandline": true, "nesting": "disabled" } }, { "command": { "action":"suggestions", "source": ["all"], "useCommandline": false, "nesting": "source" } }

即:用我已键入的内容给我近期命令建议 / 给我近期去过的目录建议 / 给我近期命令 + 已保存命令 + 本项目命令(不嵌套,全部平铺在顶层,并用已键入内容预过滤)/ 用全部来源打开并按来源分组。

JSON Schema

规格文档末尾给出了上述设置项的完整 JSON Schema 草案,这里完整保留:

"OpenSuggestionsAction": { "description": "Arguments corresponding to a Open Suggestions Action", "allOf": [ { "$ref": "#/$defs/ShortcutAction" }, { "properties": { "action": { "type": "string", "const": "suggestions" }, "source": { "$ref": "#/$defs/SuggestionSource", "description": "Which suggestion sources to filter." }, "useCommandline": { "default": false, "description": "When set to `true`, the current commandline the user has typed will prepopulate the filter of the Suggestions UI. This requires that the user has enabled shell integration in their shell's config. When set to false, the filter will start empty." }, "nesting": { "default": true, "description": "When set to `true`, suggestions will follow the provided nesting structure. For Tasks, these will follow the structure of the Command Palette. When set to `false`, no nesting will be used (and all suggestions will be in the top-level menu.", "$comment": "This setting is a possible follow-up setting, not required for v1. " } } } ] }, "BuiltinSuggestionSource": { "enum": [ "commandHistory", "directoryHistory", "tasks", "local", "all" ], "type": "string" }, "SuggestionSource": { "default": "all", "description": "Either a single suggestion source, or an array of sources to concatenate. Built-in sources include `commandHistory`, `directoryHistory`, `tasks`, and `local`. Extensions may provide additional values. The special value `all` indicates all suggestion sources should be included", "$comment": "`tasks` and `local` are sources that would be added by the Tasks feature, as a follow-up" "oneOf": [ { "type": [ "string", "null", "BuiltinSuggestionSource" ] }, { "type": "array", "items": { "type": "BuiltinSuggestionSource" } }, { "type": "array", "items": { "type": "string" } } ] }

需要注意的取值细节:source既可以是单个字符串,也可以是字符串数组(多来源拼接),特殊值all表示包含全部来源,默认值即all;扩展可以注入额外的来源字符串值(如Microsoft.Terminal.Extensions.BufferComplete)。useCommandline在 Schema 中默认值为false,而在正文的参数描述中给出的默认值口径是true——这属于规格草案在演进过程中的口径差异,读者以正文「默认 true」的语义理解其意图即可。

五、实现架构:从 Command Palette 分叉出来的 SuggestionsControl

规格的实现方案是以 Command Palette 为基础分叉(fork)出新的控件。Command Palette 本来就是 Terminal 已有的、用于展示临时命令列表并分发动作的控件,但它固定在窗口顶部中央、占据较大屏幕区域。Suggestions UI 需要:

  • 作为相对文本光标定位的控件;
  • 能以 Flyout 形式出现在 Terminal 窗口边界之外;
  • 当 UI 距离屏幕底部过近时「向上」打开——搜索框在底部,列表向上延伸;
  • 阻止其切换到命令行模式;
  • 展示建议的描述性 tooltip /TeachingTip/ 次级 flyout。

分叉而非直接复用 Command Palette,是因为可以剥掉大量与多模式(如 tab 切换器)、前缀字符切换模式相关的代码。仓库中 SuggestionsControl 的公开接口印证了这些要求:

void Open(SuggestionsMode mode, IVector<Command> commands, String filterText, Point anchor, Size space, Single characterHeight); event ... DispatchCommandRequested; // 选中建议 → 派发动作 event ... PreviewAction; // 悬停预览

Open的参数正是规格清单的落地:filterText对应useCommandline预填的过滤文本,anchor(锚点坐标)与space(可用空间)用于实现「相对光标定位 + 贴边时反向打开」,characterHeight用于对齐字符网格。而 SuggestionsControl.h 中的成员_mode(默认Palette)与_directionTopDown/BottomUp)则分别对应两种打开形态与 Crawl 阶段「支持自上而下(对齐光标行底部)或自下而上(对齐光标行顶部)打开」的勾选项。

这个菜单归谁拥有?

规格还讨论了架构所有权问题:建议菜单该由控件自身(TermControl)拥有,还是由宿主应用拥有?

  • 支持放在控件侧的理由:任何嵌入 TermControl 的消费方都应「免费」获得 shell 驱动补全菜单,不必自行重新实现。可行的改法是让控件知道菜单条目都是「发送输入」动作,并暴露一个方法,接收一组 {suggestion, name, description} 对象手动打开建议 UI;
  • 支持放在应用层的理由:像 Visual Studio 这样的宿主嵌入控件时,会希望用自己的风格与 UI 范式来呈现 shell 补全。

结论是当前由应用层(app layer)拥有该控件,待 shell 驱动补全的设计定稿后再迭代,以同时支持「使用现成建议控件」与「自带 UI」两类消费方。这也解释了为什么建议逻辑分布在 TerminalApp 一侧(构建命令列表、调用_OpenSuggestions),而 SuggestionsControl 本身只负责展示与交互。

六、实现计划:Crawl / Walk / Run / Sprint

规格将实现计划分为四个阶段(文档自述为信息性而非规范性清单):

🐣 Crawl

  • 将 Command Palette 分叉为新的 UI 元素SuggestionsControl
  • 在 Command Palette 与SuggestionsControl中启用sendInput动作的预览;
  • 支持SuggestionsControl自上而下或自下而上打开;
  • 禁用SuggestionsControl的排序——条目应由来源预先排好序;
  • TermControl上暴露近期命令的访问器;
  • 新增接受单一选项recentCommandssuggestions动作,以 MRU 顺序送入控件;
  • TermControl上暴露近期目录访问器,并新增recentDirectories来源。

🚶 Walk

  • 新增tasks来源,以所有sendInput命令的树形结构打开建议 UI;
  • 支持SuggestionsControl带/不带搜索框两种打开方式;
  • 打通 shell 驱动补全从核心层到应用层的支持;
  • 暴露TermControl当前命令行;
  • suggestions增加useCommandline属性;
  • 持久化近期命令/目录。

🏃‍♂️ Run

  • Command增加description字段;
  • 为 Suggestions UI 增加TeachingTip(或类似机制)以展示描述;
  • 使用 shell 驱动建议的ToolTip属性作为描述;
  • 增加布尔nesting属性,可禁用tasks来源的嵌套;
  • nesting支持以enabled/disabled替代true/false
  • nesting支持取值source,按建议来源分组。

🚀 Sprint

  • 扩展提供建议来源(需与 Extensions 规格一起设计);
  • Inline 模式(仅内联展示第一条建议)。规格作者认为这两项足够有野心,需要更多设计工作后才能拆解为原子任务。

七、未来考量与被否决的方案

描述提示(Description Tooltips)

为让建议携带额外上下文(如某扩展提供的命令帮助文本),设计了一种可选的 flyout:仅在 Terminal 收到了更多描述信息时才出现,且只展示当前悬停项的帮助文本,形态可以是TeachingTip。设置中的动作也可以接受可选的description属性指定该 flyout 展示的字符串。

Inline 模式(内联建议)

借鉴PsReadline的近期命令建议:针对当前提示符只显示一条内联建议(IME 幽灵文本),类似动作:

{ "command": { "action":"suggestions", "source": "commandHistory", "useCommandline": true, "inline": true } }

键入命令开头后按该键,幽灵文本立即出现;之后键入的字符送入 shell(而非隐藏 UI),终端重新查询命令行并更新过滤,Tab 补全因此仍然可用,右方向键接受建议。规格指出该模式会与 PowerShell 自身的 inline 建议处理产生冲突,不建议在 PowerShell 7 画像上启用。IDL 中被注释的// Inline,枚举值即此设想的留白。

按来源分组的前置过滤

nesting: "source"可将菜单组织为按来源分组的结构,示意如下:

Suggestions UI ├─ Recent Commands... │ ├─ git checkout main │ ├─ git fetch │ └─ git pull ├─ Recent Directories... │ ├─ d:\dev │ ├─ d:\dev\public │ └─ d:\dev\public\terminal ├─ Saved tasks... │ ├─ Git... │ │ └─ git commit -m " │ │ └─ git log... │ └─ bx & runut └─ Docker ├─ docker build --platform linux/amd64 <path> └─ docker logs -f --tail <lines_count> <container_name>

其中 "Docker" 是示意的碎片(fragment)扩展,按"source"分组时它会被单独提为顶层条目,否则其命令混在tasks里展示。

跨会话保存近期命令

近期命令若能跨会话保存(如cmd.exe历史持久化),需要:一个启用开关、控制保存上下文的设置(按 profile 还是全局、profiles.defaults如何叠加)、设置 UI 中的清除按钮;碎片不应能预填「近期命令」(那是 Tasks 或扩展专属来源的职责)。

自动 Shell Integration

上述大部分功能都依赖用户自行启用 shell integration,而自动改写用户的 shell 配置有破坏既有配置的风险,因此被明确推迟为独立规格。

被否决的想法:异步建议(Asynchronous prompting)

某些来源(如发起网络请求、或类似 Fig 解析磁盘文件的来源)可能希望异步提供结果。曾被提议让 Suggestions UI 自身充当提示输入的控件:

TerminalPage::SetUpSuggestionsUI() { const auto& asyncSource{ AsyncSuggestions() }; suggestionsUI.OnInputChanged({ asyncSource, AsyncSuggestions::InputChangedHandler}); // 在此示例中,我们不希望 UI 按输入字符串过滤条目—— // 来源已经确定了相关匹配列表。 suggestionsUI.FilterByInput(false); asyncSource.SuggestionsChanged([](const auto& newCommands){ suggestionsUI.Loading(false); suggestionsUI.Commands(newCommands); }) } void AsyncSuggestions::InputChangedHandler(FilterChangedArgs args) { // 启动一个带 trailing 的 ThrottledFunc 去发起新查询 _loadNewResults->Run(args.NewInputText()); args.RequestLoading(true); // 通过参数回传布尔值,让建议 UI 清空当前命令 // 并开始显示不确定的进度轮。 }

该方案最终被否决,主要原因有二:其一,「提示输入版本」不过滤结果,与「过滤版本」在 UX 上根本不同,难以区分;其二,异步来源与同步来源无法共存——例如source: ["tasks", "myAsyncSource"]会先展示 tasks 列表,输入后 tasks 无匹配,随后 UI 又被异步结果填满,体验割裂。

八、设计原则(Tenets)

规格文档以表格形式声明了四项设计原则:

  • 兼容性:不破坏现有流程。作为通用 UI 元素,其扩展全部由用户显式选用,不存在破坏性兼容变更;
  • 可访问性:设计目标正是让命令行 shell 建议可访问。屏幕阅读器难以处理整菜单每次全量重绘的补全 UI(因为「选中」只是 shell 侧的视觉概念),Suggestions UI 借助 WinUI 的可访问性设计为屏幕阅读器提供更贴切的上下文;
  • 可持续性:无预期变更;
  • 本地化:需求与 Command Palette 相当。经由 shell 驱动补全提供的建议是 shell 逐字告知的字符串,Terminal 无法本地化——这与 Terminal 无法本地化 PowerShell 的Get-Help输出同理,不被视为问题。

总结

Suggestions UI 是 Windows Terminal 将「终端内上下文建议」统一化的核心设计:一个以 Command Palette 为蓝本分叉、相对光标定位、支持 Palette/Menu 两种形态的临时菜单控件,其数据由commandHistorydirectoryHistorytaskslocal等来源构建为一组sendInput命令,通过source/useCommandline/nesting三个参数控制来源、预过滤与嵌套行为。仓库中 SuggestionsControl 的Open(mode, commands, filterText, anchor, space, characterHeight)接口、Command.idl 的HistoryToCommands工厂,以及 TerminalPage.cpp 中的_OpenSuggestions调用链,共同构成了从规格到实现的完整闭环。理解这条链路,也为后续跟进 shell 驱动补全、扩展建议来源与 inline 模式等演进方向打下了基础。

【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询