- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
导读
本文围绕 Warp 的specs/APP-3828产品与技术规格,深入讲解垂直标签栏(Vertical Tabs)新增的View as切换功能:在Panes(每个面板一行)与Tabs(每个标签一行,以活动面板为代表)两种行粒度之间即时切换,并作为同步设置跨会话持久化。读完本文,你将掌握该功能的完整交互设计、PaneGroup::focused_pane_id作为代表行数据源的底层原理,以及pane_ids_for_display_granularity如何以最小风险统一渲染与搜索两条代码路径的实现方案。
背景:面板中心视图的问题
Warp 的垂直标签栏原本是**面板中心(pane-centric)**的:每个可见面板(pane)在所属标签(tab)下渲染为独立的一行。当用户需要细粒度地看清每个 split 时,这种设计是合理的;但当标签内包含多个面板、而用户只想在标签层面快速扫描整个工作区时,面板行会形成噪音——每个标签组头下方排满了行,难以一眼判断"这个标签里当前正在做什么"。
APP-3828 的目标是在不引入全新视觉语言的前提下,提供一种更高层级的总览模式:把每个标签压缩为一行代表行(representative row)。它保留现有标签结构、复用现有面板行的 UI,因此第一版既易于理解又风险可控。
核心功能:View as 设置
垂直标签栏的显示选项弹出面板(display options popup)新增一个顶层设置,以两段式切换控件(two-segment toggle)呈现在弹窗顶部、现有显示控件之上:
- View as: Panes—— 默认值,即当前行为
- View as: Tabs—— 新总览模式
点击Panes或Tabs后面板立即更新,且弹窗保持打开,让用户能在上下文中实时看到变化。切换模式不会重置当前密度(Density)或任何已有面板行显示偏好。
┌─ Vertical Tabs Options ────────────┐ │ View as [ Panes | Tabs ] │ │ ─────────────────────────────── │ │ Density [ ▭ | ▭▭ ] │ │ Pane title as ▾ ... │ │ Additional metadata ▾ ... │ │ Show ▾ ... │ └────────────────────────────────────┘Panes 模式:保持现状
当View as = Panes时,面板行为与今天完全一致:
- 每个可见面板渲染为自己的条目;
- 条目按标签组头分组;
- compact / expanded 密度行为不变;
- 现有面板行显示偏好继续生效。
除弹窗中新增的View as控件外,Panes 模式不应引入任何视觉或行为变化——这是保证老用户零感知迁移的默认路径。
Tabs 模式:每标签一行
当View as = Tabs时,面板仍保持标签分组结构,但每个标签组头下方只渲染一行代表行,而不是每个可见面板一行。
代表行的数据源永远是该标签的活动面板(active pane)。因此在本迭代中,View as = Tabs实际上意味着标签条目用其**聚焦会话(Focused session)**来命名和渲染——即Focused session是 Tabs 模式下唯一支持的行为,且是隐式生效的,不提供额外命名开关。
本迭代有意不实现探索性原型(exploratory mock)中出现的 Tabs-onlyDefault name区块(含Focused session与Summary两个选项),也不引入Summary命名模式或其他替代标签命名策略;同时不会把面板扁平化为无表头的标签列表,也不会重新设计标签组头、关闭按钮、重命名或拖拽行为。
代表行的外观与更新规则
外观完全复用现有面板行 UI
代表行复用活动面板在 Panes 模式下本应使用的面板行渲染器:
- compact 密度:使用活动面板在 Panes 模式下使用的同一紧凑行渲染器;
- expanded 密度:使用同一展开行渲染器。
这包括相同的图标规则、标题规则、副标题/元数据规则、徽标(badges)、截断行为与选中样式。正因为代表行本质仍是一行"面板样式行",现有面板行显示控件在 Tabs 模式下依然生效且不会被隐藏:
Pane title as仍会改变终端代表行的标注方式;Additional metadata仍影响紧凑终端代表行;Show仍影响展开终端代表行。
代表行实时更新
每当该标签的活动面板发生变化,代表行都会立即更新,典型场景包括:
- 用户在标签内多个 split 面板间切换焦点;
- 活动面板被关闭,另一个面板成为活动面板;
- 活动面板显示的元数据变化(如终端标题、分支、徽标或未保存状态)。
Tabs 模式始终反映标签当前的活动面板,而不是用户刚切到 Tabs 模式时恰好处于活动状态的面板。
单面板标签与多面板标签的差异
- 单面板标签:标签内只有一个可见面板时,Tabs 模式与 Panes 模式在组头下方的显示效果基本一致(因为活动面板即唯一面板)。
- 多面板标签:Panes 模式为每个可见面板渲染一行;Tabs 模式仅为该标签渲染一行(基于活动面板)。非活动面板在 Tabs 模式下不会获得独立行。
Tabs 模式的交互与搜索行为
行交互
代表行沿用其复用的活动面板行的交互语义:
- 点击行:激活该标签并聚焦其活动面板;
- 选中/高亮状态:继续表示活动标签/聚焦面板。
本迭代不引入任何 Tabs 模式独有的新行级交互。
搜索/过滤
搜索过滤作用于当前模式下实际渲染的条目:
- Panes 模式下匹配保持"基于面板"的现状;
- Tabs 模式下匹配只基于每个标签的代表行。
在第一迭代中,被 Tabs 模式隐藏的非活动面板不会产生独立的匹配结果——隐藏面板不参与搜索。
标签组头完全不受影响
Tabs 模式不会移除或重新设计现有标签组头,以下行为保持原样:
- 标签标题显示;
- 面板数量显示;
- 重命名行为;
- 关闭行为;
- 拖拽行为;
- 现有组头右键菜单行为。
Tabs 模式唯一改变的是组头下方渲染的行数:每个标签一行,而非每面板一行。
源码实现:新同步设置枚举
产品行为落地的第一步,是在 app/src/workspace/tab_settings.rs 中新增行粒度枚举VerticalTabsDisplayGranularity(当前位于该文件 L344-L359),并在TabSettings中用implement_setting_for_enum!宏注册,继承既有垂直标签栏设置的同步/层级行为:
#[derive( Default, Debug, serde::Serialize, serde::Deserialize, PartialEq, Copy, Clone, schemars::JsonSchema, settings_value::SettingsValue, )] #[schemars( description = "Granularity of rows displayed in the vertical tabs panel.", rename_all = "snake_case" )] pub enum VerticalTabsDisplayGranularity { #[default] Panes, Tabs, } settings::macros::implement_setting_for_enum!( VerticalTabsDisplayGranularity, TabSettings, SupportedPlatforms::ALL, SyncToCloud::Globally(RespectUserSyncSetting::Yes), surface: settings::SettingSurfaces::GUI, private: false, toml_path: "appearance.vertical_tabs.display_granularity", description: "Granularity of rows displayed in the vertical tabs panel.", );关键点:
#[default] Panes保证了老用户默认行为零变化;SupportedPlatforms::ALL与SyncToCloud::Globally(RespectUserSyncSetting::Yes)使它成为跨会话、跨设备的同步设置(对应 PRODUCT.md 成功标准第 13 条);- TOML 配置路径为
appearance.vertical_tabs.display_granularity,与既有的view_mode、primary_info、compact_subtitle同层级(见同一文件中VerticalTabsViewMode等既有枚举的注册方式)。
值得注意:该枚举的 TOML 层级appearance.vertical_tabs.*与implement_setting_for_enum!中hierarchy归属一致,且刻意不复用/不重命名既有的VerticalTabsViewMode(它在代码里表示 compact/expanded 密度)。这是产品文案"View as"与技术枚举名之间的一次刻意解耦:UI 上把原控件改标为Density,底层枚举名暂不迁移,避免无关逻辑的连锁改动。
源码实现:WorkspaceAction 与状态持久化
Action 定义
在 app/src/workspace/action.rs(L390 附近)新增与既有垂直标签栏设置并列的 action:
SetVerticalTabsDisplayGranularity(VerticalTabsDisplayGranularity),状态写入与持久化豁免
Workspace::handle_action中对该 action 的处理与其它设置写入完全一致:读取枚举载荷 → 调用settings.vertical_tabs_display_granularity.set_value(...)→ctx.notify()触发重绘(见 app/src/workspace/view.rs 中SetVerticalTabsViewMode等既有处理器的写法)。
关键细节在于should_save_app_state_on_action:该 action 与SetVerticalTabsViewMode、SetVerticalTabsPrimaryInfo、SetVerticalTabsCompactSubtitle一样,返回false(见 action.rs 附近),因为持久化由设置框架负责,而非工作区快照。对应的回归测试位于 app/src/workspace/action_tests.rs(现有测试已覆盖SetVerticalTabsViewMode等"不保存工作区状态"的断言模式)。
弹窗鼠标状态与分段控件
VerticalTabsPanelState为新的两段式控件新增两个MouseStateHandle(Panes段与Tabs段),既有compact_segment_mouse_state/expanded_segment_mouse_state保持不变,密度控件无需重构。弹窗点击Tabs段时派发WorkspaceAction::SetVerticalTabsDisplayGranularity(granularity)(见 app/src/workspace/view/vertical_tabs.rs 附近),弹窗保持打开、原地更新。
源码实现:行粒度选择器(核心结构变更)
TECH.md 反复强调:本功能"最重要的结构性变更"是把"该标签当前应渲染/搜索哪些面板 id"的决定集中到一处。落地即为 app/src/workspace/view/vertical_tabs.rs 中的纯函数:
fn pane_ids_for_display_granularity( visible_pane_ids: &[PaneId], focused_pane_id: PaneId, granularity: VerticalTabsDisplayGranularity, ) -> Vec<PaneId> { match granularity { VerticalTabsDisplayGranularity::Panes => visible_pane_ids.to_vec(), VerticalTabsDisplayGranularity::Tabs => visible_pane_ids .iter() .copied() .find(|pane_id| *pane_id == focused_pane_id) .or_else(|| visible_pane_ids.first().copied()) .into_iter() .collect(), } }行为矩阵:
| 输入条件 | Panes 模式返回值 | Tabs 模式返回值 |
|---|---|---|
| 可见面板列表非空、聚焦面板在列表中 | 全部可见面板(保持原有顺序) | 仅聚焦面板一个 id |
| 可见面板非空、聚焦面板不在列表(关闭/恢复边界) | 全部可见面板 | 首个可见面板(防御性回退) |
| 标签无可见面板 | 空 vec | 空 vec |
该函数刻意保持纯函数、小体积、可单测,并在vertical_tabs_tests.rs中覆盖上述四种情况。
渲染与搜索:同一选择器的三处接入点
为避免"渲染一行、搜索全部"的漂移,pane_ids_for_display_granularity被统一用在三条代码路径中(均位于 app/src/workspace/view/vertical_tabs.rs):
matching_tab_indices(L1177 附近):决定哪些标签参与键盘导航与搜索结果记账。原来遍历所有可见面板,现在按粒度选择面板集合;render_groups(L1766 附近)中的搜索分支:计算matching_ids决定搜索时渲染哪些行;render_tab_group(L2061 附近)的行构建路径:决定组头下方渲染哪些行。
三处都读取同一份新设置,并替换对visible_pane_ids()的直接迭代。这保证了 Tabs 模式语义处处一致:
- 标签只渲染其代表行;
- 搜索只考虑该代表行;
- 被搜索隐藏的标签由同一代表行判定。
行渲染器与 PaneProps 保持原样
关键设计决策:本迭代不引入新的"标签行"prop 类型。代表行仍然从选中的PaneId构建普通PaneProps,并复用render_pane_row(expanded 密度)、render_compact_pane_row(compact 密度)、render_pane_row_element。这保留了:
- 行点击行为(
FocusPane); - 既有元数据与徽标规则;
- compact/expanded 密度行为;
- 选中与悬停样式;
- 与本分支上已在推进的面板行改进的兼容性。
render_tab_group同时负责组容器、可选自定义标题头、悬停背景与悬浮操作条(action belt),本迭代不 fork该结构——只有行列表随粒度变化,其余保持不变,从而把重命名、标签操作与拖拽的回归风险降到最低。
为什么代表行数据源是 focused_pane_id 而非 active_session_id
TECH.md 明确排除了active_session_id()作为代表行来源,原因有二:
active_session_id()仅适用于终端面板,在 code / notebook / workflow 标签下会失效;focused_pane_id()对任意面板类型都有效,且它正是标签重新激活时会被聚焦的面板——语义与"Focused session"完全吻合。
仓库证据:PaneGroup::focused_pane_id(app/src/pane_group/mod.rs)直接读取focus_state的focused_pane_id();而PaneGroup::display_title()(同文件 L5234 附近)早已把聚焦面板作为标签级显示状态的来源。因此当用户在 split 标签内切换焦点时,PaneGroup::focus_pane更新focus_state,下一次渲染自动产生不同的代表行——实时更新无需额外通知机制。
风险与缓解设计
| 风险 | 缓解方案 |
|---|---|
既有VerticalTabsViewMode(密度)与产品新文案View as(粒度)语义混淆 | 新增独立命名枚举VerticalTabsDisplayGranularity,不重载VerticalTabsViewMode;仅 UI 层面重标为Density |
| 关闭/恢复等边界下聚焦面板暂不在可见列表中 | 选择器回退到首个可见面板;仅当标签真正无可见面板时才返回空 vec |
| 渲染与搜索路径漂移(渲染一行却搜全部) | 三处调用路径共用同一个粒度选择器,面板 id 选择逻辑只保留一份 |
| 探索原型诱使弹窗逻辑超出范围 | 本票仅交付顶层View as控件;不添加 Tabs-onlyDefault name/Summary区块;Tabs 模式下也不隐藏既有面板行控件 |
测试与验证清单
单元测试
- app/src/workspace/view/vertical_tabs_tests.rs:新增
pane_ids_for_display_granularity测试——Panes按序返回全部;Tabs在聚焦面板存在时返回它;聚焦面板缺失时回退首个可见面板;空列表返回空; - app/src/workspace/action_tests.rs:新增
SetVerticalTabsDisplayGranularity(...)不触发工作区状态保存的测试。
手工验证
- 打开显示选项弹窗,确认
View as位于既有控件之上,切换时面板立即更新且弹窗保持打开; - 默认设置下逐可见面板渲染一行,行为与之前完全一致;
- 单面板标签在两种模式间切换,组头下方无实质变化;
- 多面板标签:Panes 显示全部面板行,Tabs 恰好一行;
- 多面板标签内切换面板焦点,Tabs 代表行随之更新;
- compact 与 expanded 两种密度下代表行均正常;
- Tabs 模式下修改
Pane title as、Additional metadata、Show,确认它们仍作用于代表行; - Tabs 模式下组头重命名、关闭、面板计数、拖拽与右键菜单正常;
- 选择 Tabs 后重启 Warp,面板以 Tabs 模式重新打开(同步设置持久化);
- 搜索:多面板标签中仅活动面板的代表行被匹配渲染。
后续工作
- 若希望代码术语与产品 UI 完全一致,可将
VerticalTabsViewMode重命名为密度相关名称(非本票必要); - 待产品行为最终确定后,在独立工单中实现 Tabs-only 命名控件(
Focused sessionvsSummary);届时Pane title as等面板行控件可改为仅针对聚焦会话路径有条件显示,而非在Tabs下始终展示。
总结
APP-3828 以"最小风险交付新语义"为设计主线:用一个新的同步设置VerticalTabsDisplayGranularity表达行粒度,用PaneGroup::focused_pane_id()作为标签级代表行的权威数据源,用纯函数pane_ids_for_display_granularity统一渲染与搜索的选型逻辑,并完全复用既有面板行渲染器。对开发者而言,这套"新增设置枚举 + 新增 action + 集中选择器 + 复用渲染器"的模式,是给大型工作区 UI 添加总览模式时值得参考的低风险样板。
- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
相关推荐
Warp 垂直标签栏「View as Panes / Tabs」技术实现:以行粒度抽象驱动双视图渲染与搜索
Warp 垂直标签栏「View as Panes / Tabs」技术实现:以行粒度抽象驱动双视图渲染与搜索 导读 本文基于开源仓库 Warp(agentic d
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体Warp 垂直标签面板控制栏(Vertical Tabs Control Bar)设计与源码实现详解
Warp 垂直标签面板控制栏(Vertical Tabs Control Bar)设计与源码实现详解 垂直标签面板(Vertical Tabs Panel)是
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体Warp 垂直标签面板新会话菜单(Vertical Tabs Dropdown)设计与实现解析
Warp 垂直标签面板新会话菜单(Vertical Tabs Dropdown)设计与实现解析 导读 Warp 在终端标签栏之上加入了可选的垂直标签面板,但初版
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考