Codex 系统手册

底部面板输入器、弹窗与 mention 输入

stage-10.1.242 个文件

这一阶段是终端聊天界面最底下那块“操作台”,属于用户干活时一直陪着的主入口。输入框负责打字、粘贴、换行、光标移动、历史找回和图片附件;草稿、提示栏、弹窗状态把临时信息分开放,避免互相搅乱。斜杠命令会被拆开、提示、补全,@ 提及会搜索文件、插件和技能并弹出候选。各种列表、设置、反馈、确认窗口共用底部面板规则,像抽屉一样弹出、接键盘、办完再收起。

本阶段的文件42

底部面板宿主与视图契约

这些文件定义底部窗格宿主、其共享视图接口,以及在聊天 UI 外公开 composer 行为的公共包装器。

tui/src/bottom_pane/bottom_pane_view.rs源码 ↗
data_modelmain loop / request handling / rendering

底部面板可以显示很多不同东西,比如让用户批准一次操作、填写一段输入、处理服务器发来的表单,或者展示一个可选列表。这个文件不做具体界面,而是规定:凡是想放到底部面板里的视图,都要像实现一份“插头标准”一样实现 BottomPaneView。它要求视图能被绘制,因为它继承了 Renderable(意思是“知道怎么把自己画到终端上”)。同时它提供一堆默认行为:默认不处理按键、默认没完成、默认不拦截请求、默认不需要定时刷新。具体视图只改自己需要的部分。ViewCompletion 用来说明视图结束是“接受”还是“取消”。这样底部面板的外层代码不用认识每一种具体视图,只要按这套接口问它:按键怎么处理、要不要关掉、是否挡住会话、是否需要用户操作。

函数细节20
BottomPaneView::handle_key_event22–22 ↗
fn handle_key_event(&mut self, _key_event: KeyEvent)

作用:当底部面板里的视图处于打开状态时,这个函数接收一次键盘按键。默认什么也不做,具体视图可以改写它来处理方向键、回车、删除等操作。

数据流:进去的是一个 KeyEvent,也就是终端捕捉到的一次按键 → 默认实现直接忽略它,不改任何状态 → 出来没有返回值;外层仍会安排重新绘制一次,以便具体实现改了状态时能显示出来。

调用关系:它是底部面板把键盘事件交给当前视图的入口。这个默认版本不把工作交给别人;真正的审批框、输入框或列表视图通常会覆盖它,写入自己的按键反应。

BottomPaneView::is_complete25–27 ↗
fn is_complete(&self) -> bool

作用:告诉外层:这个视图是不是已经办完事,可以从底部面板移除了。默认回答“没有完成”。

数据流:进去没有额外输入,只读取视图自己的状态 → 默认不检查任何东西,直接认为还没结束 → 出来是 false,表示继续显示。

调用关系:底部面板在处理按键、请求或绘制前后会用它判断是否该关掉当前视图。默认实现适合那些一直显示、直到自己明确改写完成逻辑的视图。

BottomPaneView::completion30–32 ↗
fn completion(&self) -> Option<ViewCompletion>

作用:在视图已经结束后,说明它是被接受了还是被取消了。默认没有结束原因。

数据流:进去没有参数 → 默认不读取状态,直接返回 None → 出来表示“我没有可报告的完成结果”。

调用关系:外层在视图完成后会用它区分用户点了确认还是取消。具体视图如果有确认/取消流程,就会覆盖这个函数返回 ViewCompletion::Accepted 或 ViewCompletion::Cancelled。

BottomPaneView::dismiss_after_child_accept35–37 ↗
fn dismiss_after_child_accept(&self) -> bool

作用:说明如果这个视图打开了一个子视图,并且子视图被用户接受了,自己是否也应该一起关闭。默认不关闭。

数据流:进去没有参数 → 默认直接返回 false → 出来表示父视图会继续留在底部面板里。

调用关系:它服务于“父面板打开子面板”的流程。外层在子视图接受后会询问父视图要不要顺手退场;默认版本表示没有这种联动。

BottomPaneView::clear_dismiss_after_child_accept40–40 ↗
fn clear_dismiss_after_child_accept(&mut self)

作用:当子视图被取消时,清掉父视图里可能记录的“子视图接受后我也关闭”的临时标记。默认什么也不清。

数据流:进去没有参数 → 默认不改状态 → 出来没有返回值,视图保持原样。

调用关系:它和 BottomPaneView::dismiss_after_child_accept 是一组。外层在子流程取消时会调用它,避免留下过期的关闭标记;没有这类标记的视图用默认实现即可。

BottomPaneView::view_id43–45 ↗
fn view_id(&self) -> Option<&'static str>

作用:给需要外部刷新支持的视图提供一个稳定名字。默认没有名字。

数据流:进去没有参数 → 默认直接返回 None → 出来表示外层不能用固定标识找到这个视图做刷新。

调用关系:当外部数据更新、需要刷新某个正在打开的底部视图时,外层可以靠这个标识认出它。普通视图不需要被这样定位,所以默认不提供。

BottomPaneView::selected_index49–51 ↗
fn selected_index(&self) -> Option<usize>

作用:给列表类视图报告当前选中的真实条目位置。这样刷新列表后,用户的选择可以尽量保持不跳走。

数据流:进去没有参数 → 默认返回 None → 出来表示这个视图没有可保存的列表选择位置。

调用关系:它通常配合 BottomPaneView::view_id 使用。外层刷新一个列表视图时,可以先问当前选中哪一项,再在新内容里恢复选择;非列表视图用默认值。

BottomPaneView::active_tab_id55–57 ↗
fn active_tab_id(&self) -> Option<&str>

作用:给带标签页的列表视图报告当前打开的是哪个标签页。默认没有标签页信息。

数据流:进去没有参数 → 默认返回 None → 出来表示这个视图没有活动标签页,或者不需要外层记住它。

调用关系:它是给更复杂的列表界面用的,比如一个底部面板里有多个分类页。外层刷新时可以据此恢复用户所在的标签;普通视图不会用到。

BottomPaneView::on_ctrl_c60–62 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理用户按 Ctrl-C 时的反应。Ctrl-C 通常表示取消或中断,默认回答“我不处理”。

数据流:进去没有参数,只代表当前发生了 Ctrl-C → 默认不改视图状态 → 出来是 CancellationEvent::NotHandled,表示这次取消事件要交给更外层处理。

调用关系:底部面板在捕捉到 Ctrl-C 时会先问当前视图。某些弹窗可以自己取消并关闭;默认实现把决定权留给外层的通用取消流程。

BottomPaneView::prefer_esc_to_handle_key_event66–68 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

作用:告诉外层:Esc 键应该像普通按键一样交给 handle_key_event,还是走取消流程。默认走取消流程,不特殊处理。

数据流:进去没有参数 → 默认返回 false → 出来表示 Esc 不优先交给视图自己的按键处理函数。

调用关系:它影响 Esc 键的分流。某些界面可能把 Esc 当作关闭菜单或退出编辑的小动作,所以会覆盖它;默认情况下 Esc 更像取消。

BottomPaneView::handle_paste72–74 ↗
fn handle_paste(&mut self, _pasted: String) -> bool

作用:处理用户粘贴进来的一段文字。默认不接收粘贴内容,也不要求重画。

数据流:进去是一段 pasted 字符串 → 默认忽略这段文字,不改任何内部状态 → 出来是 false,表示视图没有变化,不必因为它额外刷新。

调用关系:底部面板收到粘贴事件时会把文字交给当前视图试试。带输入框的视图通常会覆盖它,把文字塞进编辑器;没有输入能力的视图用默认实现。

BottomPaneView::flush_paste_burst_if_due80–82 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

作用:处理“连续粘贴”暂存状态到期后的收尾。这里的粘贴突发可以理解成系统把一大段粘贴拆成多次送来,视图先攒一下再统一处理。

数据流:进去没有参数 → 默认没有任何暂存粘贴状态可清理 → 出来是 false,表示状态没变。

调用关系:它让复用聊天输入框的弹窗也能参与同一套粘贴节奏控制。外层会定期问当前视图是否该把攒着的粘贴内容落地;默认视图不参与。

BottomPaneView::is_in_paste_burst88–90 ↗
fn is_in_paste_burst(&self) -> bool

作用:告诉外层当前视图是不是还在等待一批粘贴内容结束。默认不是。

数据流:进去没有参数 → 默认返回 false → 出来表示外层不用为了粘贴突发安排短暂延迟刷新。

调用关系:它配合 BottomPaneView::flush_paste_burst_if_due 使用。外层看到 true 时,会稍后再刷新一次,给后续粘贴片段一点时间;默认实现表示没有这种需要。

BottomPaneView::pre_draw_tick96–98 ↗
fn pre_draw_tick(&mut self, _now: Instant) -> bool

作用:在真正绘制前,让视图按时间推进一下自己的状态。比如倒计时、动画、超时检查,都可以放在这里。默认没有时间相关变化。

数据流:进去是当前时间 Instant → 默认忽略这个时间,不改状态 → 出来是 false,表示不需要因为时间变化而重画或结束视图。

调用关系:底部面板在每次画当前视图之前会给它一次机会更新。需要计时行为的视图会覆盖它;默认实现不调用其他逻辑。

BottomPaneView::try_consume_approval_request102–107 ↗
fn try_consume_approval_request(
        &mut self,
        request: ApprovalRequest,
    ) -> Option<ApprovalRequest>

作用:尝试让当前视图接收一个审批请求。默认不接收,并把原请求还回去。

数据流:进去是一个 ApprovalRequest,也就是需要用户批准或拒绝的请求 → 默认不处理它 → 出来是 Some(request),意思是“我没吃掉它,你拿去给别的地方处理”。

调用关系:当应用收到新的审批请求时,底部面板可能先问当前视图能不能接住。专门的审批视图会覆盖它并消费请求;默认实现让请求继续流向其他处理路径。

BottomPaneView::try_consume_user_input_request111–116 ↗
fn try_consume_user_input_request(
        &mut self,
        request: ToolRequestUserInputParams,
    ) -> Option<ToolRequestUserInputParams>

作用:尝试让当前视图接收一次“工具请求用户输入”的任务。默认不接收,并把原任务交回去。

数据流:进去是 ToolRequestUserInputParams,包含工具想向用户询问的信息 → 默认不读取也不改它 → 出来是 Some(request),表示当前视图没有处理。

调用关系:应用服务器或工具需要向用户提问时,底部面板会尝试把请求交给当前视图。能显示输入表单的视图会覆盖它;默认版本保持请求未消费。

BottomPaneView::try_consume_mcp_server_elicitation_request120–125 ↗
fn try_consume_mcp_server_elicitation_request(
        &mut self,
        request: McpServerElicitationFormRequest,
    ) -> Option<McpServerElicitationFormRequest>

作用:尝试处理来自 MCP 服务器的表单请求。MCP 可以简单理解成外部工具服务器和应用沟通的一套协议;这里的请求是服务器想让用户填点东西。

数据流:进去是 McpServerElicitationFormRequest → 默认不处理、不改变 → 出来是 Some(request),表示请求还在,需要别人继续处理。

调用关系:当外部 MCP 服务器要求用户填写受支持的表单时,底部面板会问当前视图能不能承接。表单视图会覆盖这个函数;普通视图把请求原样退回。

BottomPaneView::dismiss_app_server_request130–132 ↗
fn dismiss_app_server_request(&mut self, _request: &ResolvedAppServerRequest) -> bool

作用:当某个请求已经被另一个客户端解决时,让当前视图把相关内容关掉或更新掉。默认不变。

数据流:进去是一个 ResolvedAppServerRequest,表示外部已经完成的请求 → 默认忽略它,不改视图状态 → 出来是 false,表示当前视图没有变化。

调用关系:如果同一个会话可能被多个客户端操作,一个客户端处理完请求后,另一个客户端的底部面板需要同步消失或刷新。具体请求视图会覆盖它;默认视图不受影响。

BottomPaneView::terminal_title_requires_action139–141 ↗
fn terminal_title_requires_action(&self) -> bool

作用:告诉外层:这个视图是否代表程序正在等用户做决定。默认不是。

数据流:进去没有参数 → 默认返回 false → 出来表示终端标题不需要显示“需要操作”这类提醒。

调用关系:外层会根据它调整终端标签页标题。审批、输入表单这类会阻塞流程的视图会返回 true,让用户即使切到别的标签页也能看到 Codex 在等他。

BottomPaneView::next_frame_delay144–146 ↗
fn next_frame_delay(&self) -> Option<std::time::Duration>

作用:告诉外层下一次基于时间的重画应该等多久。默认不需要定时重画。

数据流:进去没有参数 → 默认返回 None → 出来表示外层不用为了这个视图安排下一帧。

调用关系:它用于动画、光标闪烁、倒计时等需要定时刷新但不一定有用户输入的视图。底部面板会按这个延迟安排下一次绘制;默认视图只在事件触发时刷新。

tui/src/bottom_pane/mod.rs源码 ↗
domain_logicmain loop / user input handling / rendering

底部面板就像聊天软件最下面那一块:用户在这里打字,也会在这里看到“正在工作”、命令是否批准、候选列表、排队输入、图片附件等提示。它很重要,因为用户按一个键,可能是关闭弹窗,也可能是中断正在跑的任务;如果判断错了,就会把任务误停掉,或者把该显示的输入框藏起来。这段测试专门检查这些容易出错的场景:审批弹窗延迟出现后还能用快捷键确认;服务端取消请求时能删掉对应弹窗;任务运行时状态栏和输入框位置正确;Esc 键在有弹窗时只关弹窗,没有弹窗时才中断任务;改过快捷键后也按新规则走。整体上,它是在给底部面板的“交通规则”做验收,保证用户操作不会被误解。

函数细节196
BottomPane::new255–299 ↗
BottomPane::set_skills301–304 ↗
fn set_skills(&mut self, skills: Option<Vec<SkillMetadata>>)

调用图:调用 2 个内部函数(request_redraw, set_skill_mentions)。

BottomPane::set_image_paste_enabled309–312 ↗
fn set_image_paste_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_image_paste_enabled)。

BottomPane::set_connectors_snapshot314–317 ↗
fn set_connectors_snapshot(&mut self, snapshot: Option<ConnectorsSnapshot>)

调用图:调用 2 个内部函数(request_redraw, set_connector_mentions)。

BottomPane::set_plugin_mentions319–322 ↗
fn set_plugin_mentions(&mut self, plugins: Option<Vec<PluginCapabilitySummary>>)

调用图:调用 2 个内部函数(request_redraw, set_plugin_mentions);被 2 处调用(on_plugin_mentions_loaded, refresh_plugin_mentions)。

BottomPane::set_plugins_command_enabled324–327 ↗
fn set_plugins_command_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_plugins_command_enabled)。

BottomPane::set_token_activity_command_enabled329–332 ↗
fn set_token_activity_command_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_token_activity_command_enabled)。

BottomPane::set_mentions_v2_enabled334–337 ↗
fn set_mentions_v2_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_mentions_v2_enabled)。

BottomPane::take_mention_bindings339–341 ↗
fn take_mention_bindings(&mut self) -> Vec<MentionBinding>

调用图:调用 1 个内部函数(take_mention_bindings);被 1 处调用(drain_pending_submission_state)。

BottomPane::take_recent_submission_mention_bindings343–345 ↗
fn take_recent_submission_mention_bindings(&mut self) -> Vec<MentionBinding>

调用图:调用 1 个内部函数(take_recent_submission_mention_bindings);被 1 处调用(drain_pending_submission_state)。

BottomPane::record_pending_slash_command_history351–353 ↗
fn record_pending_slash_command_history(&mut self)

调用图:调用 1 个内部函数(record_pending_slash_command_history)。

BottomPane::set_keymap_bindings362–372 ↗
fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap)

调用图:调用 4 个内部函数(request_redraw, set_keymap_bindings, set_interrupt_binding, primary_binding);外部调用 1 个(clone)。

BottomPane::drain_pending_submission_state375–380 ↗
fn drain_pending_submission_state(&mut self)

调用图:调用 4 个内部函数(take_mention_bindings, take_recent_submission_images_with_placeholders, take_recent_submission_mention_bindings, take_remote_image_urls)。

BottomPane::set_collaboration_modes_enabled382–385 ↗
fn set_collaboration_modes_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_collaboration_modes_enabled)。

BottomPane::set_connectors_enabled387–389 ↗
fn set_connectors_enabled(&mut self, enabled: bool)

调用图:调用 1 个内部函数(set_connectors_enabled)。

BottomPane::set_windows_degraded_sandbox_active392–395 ↗
fn set_windows_degraded_sandbox_active(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_windows_degraded_sandbox_active)。

BottomPane::set_collaboration_mode_indicator397–403 ↗
fn set_collaboration_mode_indicator(
        &mut self,
        indicator: Option<CollaborationModeIndicator>,
    )

调用图:调用 2 个内部函数(request_redraw, set_collaboration_mode_indicator)。

BottomPane::set_goal_status_indicator405–408 ↗
fn set_goal_status_indicator(&mut self, indicator: Option<GoalStatusIndicator>)

调用图:调用 2 个内部函数(request_redraw, set_goal_status_indicator)。

BottomPane::set_ide_context_active410–413 ↗
fn set_ide_context_active(&mut self, active: bool)

调用图:调用 2 个内部函数(request_redraw, set_ide_context_active)。

BottomPane::set_personality_command_enabled415–418 ↗
fn set_personality_command_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_personality_command_enabled)。

BottomPane::set_service_tier_commands_enabled420–423 ↗
fn set_service_tier_commands_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_service_tier_commands_enabled)。

BottomPane::set_service_tier_commands425–428 ↗
fn set_service_tier_commands(&mut self, commands: Vec<ServiceTierCommand>)

调用图:调用 2 个内部函数(request_redraw, set_service_tier_commands)。

BottomPane::set_goal_command_enabled430–433 ↗
fn set_goal_command_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_goal_command_enabled)。

BottomPane::set_side_conversation_active435–438 ↗
fn set_side_conversation_active(&mut self, active: bool)

调用图:调用 2 个内部函数(request_redraw, set_side_conversation_active)。

BottomPane::set_placeholder_text440–443 ↗
fn set_placeholder_text(&mut self, placeholder: String)

调用图:调用 2 个内部函数(request_redraw, set_placeholder_text)。

BottomPane::set_queued_message_edit_binding447–450 ↗
fn set_queued_message_edit_binding(&mut self, binding: Option<KeyBinding>)

调用图:调用 2 个内部函数(request_redraw, set_edit_binding)。

BottomPane::set_vim_enabled452–455 ↗
fn set_vim_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_vim_enabled)。

BottomPane::toggle_vim_enabled457–461 ↗
fn toggle_vim_enabled(&mut self) -> bool

调用图:调用 2 个内部函数(request_redraw, toggle_vim_enabled);被 1 处调用(toggle_vim_mode_and_notify)。

BottomPane::status_widget463–465 ↗
fn status_widget(&self) -> Option<&StatusIndicatorWidget>
BottomPane::skills467–469 ↗
fn skills(&self) -> Option<&Vec<SkillMetadata>>

调用图:调用 1 个内部函数(skills)。

BottomPane::plugins471–473 ↗
fn plugins(&self) -> Option<&Vec<PluginCapabilitySummary>>

调用图:调用 1 个内部函数(plugins);被 2 处调用(on_plugin_mentions_loaded, plugins_for_mentions)。

BottomPane::context_window_percent476–478 ↗
fn context_window_percent(&self) -> Option<i64>
BottomPane::context_window_used_tokens481–483 ↗
fn context_window_used_tokens(&self) -> Option<i64>
BottomPane::active_view485–487 ↗
fn active_view(&self) -> Option<&dyn BottomPaneView>

调用图:被 3 处调用(as_renderable_with_composer_right_reserve, schedule_active_view_frame, terminal_title_requires_action)。

BottomPane::push_view489–493 ↗
BottomPane::pop_active_view_with_completion495–516 ↗
fn pop_active_view_with_completion(&mut self, completion: Option<ViewCompletion>)

调用图:调用 1 个内部函数(on_view_stack_depth_decreased);被 2 处调用(handle_key_event, on_ctrl_c)。

BottomPane::on_view_stack_depth_decreased518–522 ↗
fn on_view_stack_depth_decreased(&mut self)

调用图:调用 1 个内部函数(on_active_view_complete);被 2 处调用(dismiss_app_server_request, pop_active_view_with_completion)。

BottomPane::approval_prompt_delay_remaining524–531 ↗
fn approval_prompt_delay_remaining(&self, now: Instant) -> Option<Duration>

调用图:被 3 处调用(maybe_show_delayed_approval_requests_at, push_approval_request, record_composer_activity_at)。

BottomPane::record_composer_activity_at533–540 ↗
fn record_composer_activity_at(&mut self, now: Instant)

调用图:调用 2 个内部函数(approval_prompt_delay_remaining, request_redraw_in);被 2 处调用(handle_key_event, handle_paste);外部调用 1 个(is_empty)。

BottomPane::maybe_show_delayed_approval_requests_at542–569 ↗
fn maybe_show_delayed_approval_requests_at(&mut self, now: Instant)

调用图:调用 5 个内部函数(approval_prompt_delay_remaining, pause_status_timer_for_modal, push_view, request_redraw_in, new);被 2 处调用(pre_draw_tick_at, push_approval_request);外部调用 5 个(new, is_empty, pop_back, pop_front, clone)。

BottomPane::handle_key_event572–664 ↗
fn handle_key_event(&mut self, key_event: KeyEvent) -> InputResult

调用图:调用 11 个内部函数(composer_should_handle_vim_insert_escape, composer_text, pop_active_view_with_completion, record_composer_activity_at, request_redraw, request_redraw_in, handle_key_event, is_in_paste_burst, popup_active, recommended_paste_flush_delay (+1 more));外部调用 2 个(now, matches!)。

BottomPane::on_ctrl_c675–700 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

调用图:调用 6 个内部函数(clear_composer_for_ctrl_c, composer_is_empty, pop_active_view_with_completion, request_redraw, show_quit_shortcut_hint, ctrl);外部调用 3 个(Char, matches!, cancel_history_search)。

BottomPane::handle_paste702–723 ↗
fn handle_paste(&mut self, pasted: String)

调用图:调用 4 个内部函数(on_active_view_complete, record_composer_activity_at, request_redraw, handle_paste);外部调用 1 个(now)。

BottomPane::insert_str725–728 ↗
fn insert_str(&mut self, text: &str)

调用图:调用 2 个内部函数(request_redraw, insert_str);被 1 处调用(insert_str)。

BottomPane::pre_draw_tick730–732 ↗
fn pre_draw_tick(&mut self)

调用图:调用 1 个内部函数(pre_draw_tick_at);被 1 处调用(pre_draw_tick);外部调用 1 个(now)。

BottomPane::pre_draw_tick_at734–739 ↗
fn pre_draw_tick_at(&mut self, now: Instant)

调用图:调用 4 个内部函数(maybe_show_delayed_approval_requests_at, schedule_active_view_frame, tick_active_view, sync_popups);被 1 处调用(pre_draw_tick)。

BottomPane::tick_active_view741–754 ↗
fn tick_active_view(&mut self, now: Instant)

调用图:调用 2 个内部函数(on_active_view_complete, request_redraw);被 1 处调用(pre_draw_tick_at)。

BottomPane::schedule_active_view_frame756–763 ↗
fn schedule_active_view_frame(&self)

调用图:调用 2 个内部函数(active_view, request_redraw_in);被 4 处调用(dismiss_view_by_id, pre_draw_tick_at, push_view, replace_selection_view_if_present)。

BottomPane::set_composer_text770–780 ↗
fn set_composer_text(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
    )

调用图:调用 3 个内部函数(request_redraw, move_cursor_to_end, set_text_content);被 1 处调用(set_composer_text)。

BottomPane::set_composer_text_with_mention_bindings787–802 ↗
fn set_composer_text_with_mention_bindings(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        mention_bindings: Vec<Men

调用图:调用 3 个内部函数(request_redraw, move_cursor_to_end, set_text_content_with_mention_bindings)。

BottomPane::set_composer_input_enabled805–812 ↗
fn set_composer_input_enabled(
        &mut self,
        enabled: bool,
        placeholder: Option<String>,
    )

调用图:调用 2 个内部函数(request_redraw, set_input_enabled);被 3 处调用(on_active_view_complete, push_mcp_server_elicitation_request, push_user_input_request)。

BottomPane::show_shutdown_in_progress814–818 ↗
fn show_shutdown_in_progress(&mut self)

调用图:调用 2 个内部函数(request_redraw, show_shutdown_in_progress);被 1 处调用(show_shutdown_in_progress)。

BottomPane::clear_composer_for_ctrl_c820–832 ↗
fn clear_composer_for_ctrl_c(&mut self)

调用图:调用 3 个内部函数(send, request_redraw, clear_for_ctrl_c);被 1 处调用(on_ctrl_c);外部调用 1 个(warn!)。

BottomPane::composer_text835–837 ↗
fn composer_text(&self) -> String

调用图:调用 1 个内部函数(current_text);被 1 处调用(handle_key_event)。

BottomPane::composer_cursor840–842 ↗
fn composer_cursor(&self) -> usize

调用图:调用 1 个内部函数(cursor)。

BottomPane::composer_draft_snapshot844–846 ↗
fn composer_draft_snapshot(&self) -> chat_composer::ComposerDraftSnapshot

调用图:调用 1 个内部函数(draft_snapshot)。

BottomPane::composer_text_elements849–851 ↗
fn composer_text_elements(&self) -> Vec<TextElement>

调用图:调用 1 个内部函数(text_elements)。

BottomPane::composer_local_images853–855 ↗
fn composer_local_images(&self) -> Vec<LocalImageAttachment>

调用图:调用 1 个内部函数(local_images)。

BottomPane::composer_local_image_paths858–860 ↗
fn composer_local_image_paths(&self) -> Vec<PathBuf>

调用图:调用 1 个内部函数(local_image_paths)。

BottomPane::composer_text_with_pending862–864 ↗
fn composer_text_with_pending(&self) -> String

调用图:调用 1 个内部函数(current_text_with_pending)。

BottomPane::composer_input_enabled867–869 ↗
fn composer_input_enabled(&self) -> bool

调用图:调用 1 个内部函数(input_enabled)。

BottomPane::composer_pending_pastes871–873 ↗
fn composer_pending_pastes(&self) -> Vec<(String, String)>

调用图:调用 1 个内部函数(pending_pastes)。

BottomPane::apply_external_edit875–878 ↗
fn apply_external_edit(&mut self, text: String)

调用图:调用 2 个内部函数(request_redraw, apply_external_edit)。

BottomPane::set_plan_mode_nudge_visible886–890 ↗
fn set_plan_mode_nudge_visible(&mut self, visible: bool)

调用图:调用 2 个内部函数(request_redraw, set_plan_mode_nudge_visible)。

BottomPane::plan_mode_nudge_visible893–895 ↗
fn plan_mode_nudge_visible(&self) -> bool

调用图:调用 1 个内部函数(plan_mode_nudge_visible)。

BottomPane::set_remote_image_urls897–900 ↗
fn set_remote_image_urls(&mut self, urls: Vec<String>)

调用图:调用 2 个内部函数(request_redraw, set_remote_image_urls);被 1 处调用(set_remote_image_urls)。

BottomPane::remote_image_urls902–904 ↗
fn remote_image_urls(&self) -> Vec<String>

调用图:调用 1 个内部函数(remote_image_urls);被 1 处调用(remote_image_urls)。

BottomPane::take_remote_image_urls906–910 ↗
fn take_remote_image_urls(&mut self) -> Vec<String>

调用图:调用 2 个内部函数(request_redraw, take_remote_image_urls);被 2 处调用(drain_pending_submission_state, take_remote_image_urls)。

BottomPane::set_composer_pending_pastes912–915 ↗
fn set_composer_pending_pastes(&mut self, pending_pastes: Vec<(String, String)>)

调用图:调用 2 个内部函数(request_redraw, set_pending_pastes)。

BottomPane::update_status920–932 ↗
fn update_status(
        &mut self,
        header: String,
        details: Option<String>,
        details_capitalization: StatusDetailsCapitalization,
        details_max_lines: usize,
    )

调用图:调用 1 个内部函数(request_redraw)。

BottomPane::show_quit_shortcut_hint940–962 ↗
fn show_quit_shortcut_hint(&mut self, key: KeyBinding)

调用图:调用 2 个内部函数(request_redraw, show_quit_shortcut_hint);被 1 处调用(on_ctrl_c);外部调用 4 个(spawn, try_current, sleep, clone)。

BottomPane::clear_quit_shortcut_hint965–968 ↗
fn clear_quit_shortcut_hint(&mut self)

调用图:调用 2 个内部函数(request_redraw, clear_quit_shortcut_hint)。

BottomPane::quit_shortcut_hint_visible971–973 ↗
fn quit_shortcut_hint_visible(&self) -> bool

调用图:调用 1 个内部函数(quit_shortcut_hint_visible)。

BottomPane::status_indicator_visible976–978 ↗
fn status_indicator_visible(&self) -> bool
BottomPane::status_line_text981–983 ↗
fn status_line_text(&self) -> Option<String>

调用图:调用 1 个内部函数(status_line_text);被 1 处调用(status_line_text)。

BottomPane::show_esc_backtrack_hint985–989 ↗
fn show_esc_backtrack_hint(&mut self)

调用图:调用 2 个内部函数(request_redraw, set_esc_backtrack_hint);被 1 处调用(show_esc_backtrack_hint)。

BottomPane::clear_esc_backtrack_hint991–997 ↗
fn clear_esc_backtrack_hint(&mut self)

调用图:调用 2 个内部函数(request_redraw, set_esc_backtrack_hint);被 1 处调用(clear_esc_backtrack_hint)。

BottomPane::set_task_running1001–1026 ↗
fn set_task_running(&mut self, running: bool)

调用图:调用 6 个内部函数(hide_status_indicator, request_redraw, sync_status_inline_message, set_task_running, primary_binding, new);被 2 处调用(enter_review_mode_with_hint, submit_op);外部调用 2 个(clone, clone)。

BottomPane::set_queue_submissions1028–1030 ↗
fn set_queue_submissions(&mut self, queue_submissions: bool)

调用图:调用 1 个内部函数(set_queue_submissions)。

BottomPane::hide_status_indicator1033–1037 ↗
fn hide_status_indicator(&mut self)

调用图:调用 1 个内部函数(request_redraw);被 1 处调用(set_task_running)。

BottomPane::ensure_status_indicator1039–1052 ↗
fn ensure_status_indicator(&mut self)

调用图:调用 4 个内部函数(request_redraw, sync_status_inline_message, primary_binding, new);外部调用 2 个(clone, clone)。

BottomPane::set_interrupt_hint_visible1054–1059 ↗
fn set_interrupt_hint_visible(&mut self, visible: bool)

调用图:调用 1 个内部函数(request_redraw)。

BottomPane::set_context_window1061–1072 ↗
fn set_context_window(&mut self, percent: Option<i64>, used_tokens: Option<i64>)

调用图:调用 2 个内部函数(request_redraw, set_context_window);被 3 处调用(apply_token_info, restore_pre_review_token_info, set_token_info)。

BottomPane::show_selection_view1075–1086 ↗
fn show_selection_view(
        &mut self,
        mut params: list_selection_view::SelectionViewParams,
    )

调用图:调用 3 个内部函数(apply_standard_popup_hint, push_view, new);被 3 处调用(open_feedback_consent, open_memories_enable_prompt, open_multi_agent_enable_prompt);外部调用 2 个(new, clone)。

BottomPane::apply_standard_popup_hint1088–1102 ↗
fn apply_standard_popup_hint(&self, params: &mut list_selection_view::SelectionViewParams)

调用图:调用 2 个内部函数(standard_popup_hint_line, standard_popup_hint_line);被 4 处调用(replace_active_views_with_selection_view, replace_selection_view_if_active, replace_selection_view_if_present, show_selection_view)。

BottomPane::replace_selection_view_if_active1105–1127 ↗
fn replace_selection_view_if_active(
        &mut self,
        view_id: &'static str,
        mut params: list_selection_view::SelectionViewParams,
    ) -> bool

调用图:调用 3 个内部函数(apply_standard_popup_hint, push_view, new);外部调用 2 个(new, clone)。

BottomPane::replace_selection_view_if_present1130–1155 ↗
fn replace_selection_view_if_present(
        &mut self,
        view_id: &'static str,
        mut params: list_selection_view::SelectionViewParams,
    ) -> bool

调用图:调用 4 个内部函数(apply_standard_popup_hint, request_redraw, schedule_active_view_frame, new);外部调用 2 个(new, clone)。

BottomPane::standard_popup_hint_line1157–1159 ↗
fn standard_popup_hint_line(&self) -> Line<'static>

调用图:调用 1 个内部函数(standard_popup_hint_line_for_keymap);被 1 处调用(apply_standard_popup_hint)。

BottomPane::list_keymap1161–1163 ↗
fn list_keymap(&self) -> crate::keymap::ListKeymap

调用图:被 2 处调用(open_app_link_view, open_memories_popup)。

BottomPane::replace_active_views_with_selection_view1167–1197 ↗
fn replace_active_views_with_selection_view(
        &mut self,
        view_ids: &[&'static str],
        mut params: list_selection_view::SelectionViewParams,
    ) -> bool

调用图:调用 3 个内部函数(apply_standard_popup_hint, push_view, new);外部调用 2 个(new, clone)。

BottomPane::selected_index_for_active_view1199–1204 ↗
fn selected_index_for_active_view(&self, view_id: &'static str) -> Option<usize>
BottomPane::active_tab_id_for_active_view1206–1211 ↗
fn active_tab_id_for_active_view(&self, view_id: &'static str) -> Option<&str>
BottomPane::dismiss_active_view_if_id1213–1225 ↗
fn dismiss_active_view_if_id(&mut self, view_id: &'static str) -> bool

调用图:调用 1 个内部函数(request_redraw)。

BottomPane::dismiss_view_by_id1228–1244 ↗
fn dismiss_view_by_id(&mut self, view_id: &'static str) -> bool

调用图:调用 2 个内部函数(request_redraw, schedule_active_view_frame)。

BottomPane::set_pending_input_preview1247–1257 ↗
fn set_pending_input_preview(
        &mut self,
        queued: Vec<String>,
        pending_steers: Vec<String>,
        rejected_steers: Vec<String>,
    )

调用图:调用 1 个内部函数(request_redraw)。

BottomPane::set_pending_thread_approvals1260–1264 ↗
fn set_pending_thread_approvals(&mut self, threads: Vec<String>)

调用图:调用 2 个内部函数(request_redraw, set_threads);被 1 处调用(set_pending_thread_approvals)。

BottomPane::pending_thread_approvals1267–1269 ↗
fn pending_thread_approvals(&self) -> &[String]

调用图:调用 1 个内部函数(threads);被 1 处调用(pending_thread_approvals)。

BottomPane::set_unified_exec_processes1275–1280 ↗
fn set_unified_exec_processes(&mut self, processes: Vec<String>)

调用图:调用 3 个内部函数(request_redraw, sync_status_inline_message, set_processes)。

BottomPane::sync_status_inline_message1286–1290 ↗
fn sync_status_inline_message(&mut self)

调用图:调用 1 个内部函数(summary_text);被 3 处调用(ensure_status_indicator, set_task_running, set_unified_exec_processes)。

BottomPane::composer_is_empty1292–1294 ↗
fn composer_is_empty(&self) -> bool

调用图:调用 1 个内部函数(is_empty);被 2 处调用(on_ctrl_c, composer_is_empty)。

BottomPane::composer_is_vim_enabled1297–1299 ↗
fn composer_is_vim_enabled(&self) -> bool

调用图:调用 1 个内部函数(is_vim_enabled)。

BottomPane::composer_should_handle_vim_insert_escape1301–1303 ↗
fn composer_should_handle_vim_insert_escape(&self, key_event: KeyEvent) -> bool

调用图:调用 1 个内部函数(should_handle_vim_insert_escape);被 2 处调用(handle_key_event, should_handle_vim_insert_escape)。

BottomPane::is_task_running1305–1307 ↗
fn is_task_running(&self) -> bool

调用图:被 3 处调用(enter_review_mode_with_hint, is_task_running_for_test, submit_op)。

BottomPane::terminal_title_requires_action1309–1312 ↗
fn terminal_title_requires_action(&self) -> bool

调用图:调用 1 个内部函数(active_view)。

BottomPane::has_active_view1314–1316 ↗
fn has_active_view(&self) -> bool

调用图:被 1 处调用(has_active_view)。

BottomPane::active_view_id1319–1321 ↗
fn active_view_id(&self) -> Option<&'static str>
BottomPane::is_normal_backtrack_mode1326–1328 ↗
fn is_normal_backtrack_mode(&self) -> bool

调用图:调用 1 个内部函数(popup_active);被 1 处调用(is_normal_backtrack_mode)。

BottomPane::can_launch_external_editor1331–1333 ↗
fn can_launch_external_editor(&self) -> bool

调用图:调用 1 个内部函数(popup_active);被 1 处调用(no_modal_or_popup_active)。

BottomPane::no_modal_or_popup_active1340–1342 ↗
fn no_modal_or_popup_active(&self) -> bool

调用图:调用 1 个内部函数(can_launch_external_editor)。

BottomPane::show_view1344–1346 ↗
fn show_view(&mut self, view: Box<dyn BottomPaneView>)

调用图:调用 1 个内部函数(push_view);被 3 处调用(open_app_link_view, open_memories_popup, show_feedback_note)。

BottomPane::push_approval_request1349–1384 ↗
fn push_approval_request(&mut self, request: ApprovalRequest, features: &Features)

调用图:调用 6 个内部函数(approval_prompt_delay_remaining, maybe_show_delayed_approval_requests_at, pause_status_timer_for_modal, push_view, request_redraw, new);外部调用 6 个(new, now, is_empty, push_back, clone, clone)。

BottomPane::push_user_input_request1387–1414 ↗
fn push_user_input_request(&mut self, request: ToolRequestUserInputParams)

调用图:调用 5 个内部函数(pause_status_timer_for_modal, push_view, request_redraw, set_composer_input_enabled, new_with_keymap);外部调用 3 个(new, clone, clone)。

BottomPane::push_mcp_server_elicitation_request1416–1501 ↗
fn push_mcp_server_elicitation_request(
        &mut self,
        request: McpServerElicitationFormRequest,
    )

调用图:调用 10 个内部函数(pause_status_timer_for_modal, push_view, request_redraw, set_composer_input_enabled, new_with_keymap, request_id, server_name, thread_id, tool_suggestion, new_with_keymap);外部调用 4 个(new, matches!, clone, unreachable!)。

BottomPane::dismiss_app_server_request1503–1540 ↗
fn dismiss_app_server_request(
        &mut self,
        request: &ResolvedAppServerRequest,
    ) -> bool

调用图:调用 2 个内部函数(on_view_stack_depth_decreased, request_redraw);被 1 处调用(dismiss_app_server_request);外部调用 3 个(new, len, retain)。

BottomPane::on_active_view_complete1542–1545 ↗
fn on_active_view_complete(&mut self)

调用图:调用 2 个内部函数(resume_status_timer_after_modal, set_composer_input_enabled);被 3 处调用(handle_paste, on_view_stack_depth_decreased, tick_active_view)。

BottomPane::pause_status_timer_for_modal1547–1551 ↗
fn pause_status_timer_for_modal(&mut self)

调用图:被 4 处调用(maybe_show_delayed_approval_requests_at, push_approval_request, push_mcp_server_elicitation_request, push_user_input_request)。

BottomPane::resume_status_timer_after_modal1553–1557 ↗
fn resume_status_timer_after_modal(&mut self)

调用图:被 1 处调用(on_active_view_complete)。

BottomPane::request_redraw1560–1562 ↗
BottomPane::request_redraw_in1564–1566 ↗
fn request_redraw_in(&self, dur: Duration)

调用图:调用 1 个内部函数(schedule_frame_in);被 4 处调用(handle_key_event, maybe_show_delayed_approval_requests_at, record_composer_activity_at, schedule_active_view_frame)。

BottomPane::set_history_metadata1570–1579 ↗
fn set_history_metadata(
        &mut self,
        thread_id: ThreadId,
        log_id: u64,
        entry_count: usize,
    )

调用图:调用 1 个内部函数(set_history_metadata)。

BottomPane::flush_paste_burst_if_due1581–1590 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

调用图:调用 1 个内部函数(flush_paste_burst_if_due)。

BottomPane::is_in_paste_burst1592–1599 ↗
fn is_in_paste_burst(&self) -> bool

调用图:调用 1 个内部函数(is_in_paste_burst)。

BottomPane::on_history_entry_response1601–1615 ↗
fn on_history_entry_response(
        &mut self,
        log_id: u64,
        offset: usize,
        entry: Option<String>,
    )

调用图:调用 3 个内部函数(request_redraw, on_history_entry_response, sync_popups);被 1 处调用(handle_history_entry_response)。

BottomPane::record_replayed_user_message_history1617–1619 ↗
fn record_replayed_user_message_history(&mut self, entry: HistoryEntry)

调用图:调用 1 个内部函数(record_replayed_user_message_history);被 1 处调用(on_committed_user_message)。

BottomPane::on_file_search_result1621–1624 ↗
fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>)

调用图:调用 2 个内部函数(request_redraw, on_file_search_result);被 1 处调用(apply_file_search_result)。

BottomPane::attach_image1626–1631 ↗
fn attach_image(&mut self, path: PathBuf)

调用图:调用 2 个内部函数(request_redraw, attach_image)。

BottomPane::take_recent_submission_images1634–1636 ↗
fn take_recent_submission_images(&mut self) -> Vec<PathBuf>

调用图:调用 1 个内部函数(take_recent_submission_images)。

BottomPane::take_recent_submission_images_with_placeholders1638–1643 ↗
fn take_recent_submission_images_with_placeholders(
        &mut self,
    ) -> Vec<LocalImageAttachment>

调用图:调用 1 个内部函数(take_recent_submission_images_with_placeholders);被 1 处调用(drain_pending_submission_state)。

BottomPane::prepare_inline_args_submission1645–1650 ↗
fn prepare_inline_args_submission(
        &mut self,
        record_history: bool,
    ) -> Option<(String, Vec<TextElement>)>

调用图:调用 1 个内部函数(prepare_inline_args_submission)。

BottomPane::as_renderable1652–1654 ↗
fn as_renderable(&'_ self) -> RenderableItem<'_>

调用图:调用 1 个内部函数(as_renderable_with_composer_right_reserve);被 4 处调用(cursor_pos, cursor_style, desired_height, render)。

BottomPane::as_renderable_with_composer_right_reserve1656–1712 ↗
fn as_renderable_with_composer_right_reserve(
        &'_ self,
        composer_right_reserve: u16,
    ) -> RenderableItem<'_>

调用图:调用 4 个内部函数(active_view, is_empty, is_empty, new);被 5 处调用(as_renderable, cursor_pos_with_composer_right_reserve, cursor_style_with_composer_right_reserve, desired_height_with_composer_right_reserve, render_with_composer_right_reserve);外部调用 3 个(new, Borrowed, Owned)。

BottomPane::render_with_composer_right_reserve1714–1722 ↗
fn render_with_composer_right_reserve(
        &self,
        area: Rect,
        buf: &mut Buffer,
        composer_right_reserve: u16,
    )

调用图:调用 1 个内部函数(as_renderable_with_composer_right_reserve);被 1 处调用(render)。

BottomPane::desired_height_with_composer_right_reserve1724–1731 ↗
fn desired_height_with_composer_right_reserve(
        &self,
        width: u16,
        composer_right_reserve: u16,
    ) -> u16

调用图:调用 1 个内部函数(as_renderable_with_composer_right_reserve);被 1 处调用(desired_height)。

BottomPane::cursor_pos_with_composer_right_reserve1733–1740 ↗
fn cursor_pos_with_composer_right_reserve(
        &self,
        area: Rect,
        composer_right_reserve: u16,
    ) -> Option<(u16, u16)>

调用图:调用 1 个内部函数(as_renderable_with_composer_right_reserve);被 1 处调用(cursor_pos)。

BottomPane::cursor_style_with_composer_right_reserve1742–1749 ↗
fn cursor_style_with_composer_right_reserve(
        &self,
        area: Rect,
        composer_right_reserve: u16,
    ) -> crossterm::cursor::SetCursorStyle

调用图:调用 1 个内部函数(as_renderable_with_composer_right_reserve);被 1 处调用(cursor_style)。

BottomPane::set_status_line1751–1755 ↗
fn set_status_line(&mut self, status_line: Option<Line<'static>>)

调用图:调用 2 个内部函数(request_redraw, set_status_line)。

BottomPane::set_status_line_enabled1763–1767 ↗
fn set_status_line_enabled(&mut self, enabled: bool)

调用图:调用 2 个内部函数(request_redraw, set_status_line_enabled)。

BottomPane::set_active_agent_label1773–1777 ↗
fn set_active_agent_label(&mut self, active_agent_label: Option<String>)

调用图:调用 2 个内部函数(request_redraw, set_active_agent_label)。

BottomPane::set_side_conversation_context_label1779–1783 ↗
fn set_side_conversation_context_label(&mut self, label: Option<String>)

调用图:调用 2 个内部函数(request_redraw, set_side_conversation_context_label)。

ChatComposerRightReserveRenderable::render1792–1799 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

调用图:调用 1 个内部函数(render_with_mask_and_textarea_right_reserve)。

ChatComposerRightReserveRenderable::desired_height1801–1804 ↗
fn desired_height(&self, width: u16) -> u16

调用图:调用 1 个内部函数(desired_height_with_textarea_right_reserve)。

ChatComposerRightReserveRenderable::cursor_pos1806–1809 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

调用图:调用 1 个内部函数(cursor_pos_with_textarea_right_reserve)。

ChatComposerRightReserveRenderable::cursor_style1811–1813 ↗
fn cursor_style(&self, area: Rect) -> crossterm::cursor::SetCursorStyle

调用图:调用 1 个内部函数(cursor_style)。

BottomPane::render1817–1819 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

调用图:调用 1 个内部函数(as_renderable);被 1 处调用(render_snapshot)。

BottomPane::desired_height1820–1822 ↗
fn desired_height(&self, width: u16) -> u16

调用图:调用 1 个内部函数(as_renderable)。

BottomPane::cursor_pos1823–1825 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

调用图:调用 1 个内部函数(as_renderable)。

BottomPane::cursor_style1827–1829 ↗
fn cursor_style(&self, area: Rect) -> crossterm::cursor::SetCursorStyle

调用图:调用 1 个内部函数(as_renderable)。

tests::snapshot_buffer1856–1866 ↗
fn snapshot_buffer(buf: &Buffer) -> String

调用图:外部调用 3 个(area, new, new)。

tests::render_snapshot1868–1872 ↗
fn render_snapshot(pane: &BottomPane, area: Rect) -> String

调用图:调用 1 个内部函数(render);外部调用 2 个(empty, snapshot_buffer)。

tests::test_pane1874–1876 ↗
fn test_pane(app_event_tx: AppEventSender) -> BottomPane

调用图:外部调用 1 个(test_pane_with_disable_paste_burst)。

tests::test_pane_with_disable_paste_burst1878–1892 ↗
fn test_pane_with_disable_paste_burst(
        app_event_tx: AppEventSender,
        disable_paste_burst: bool,
    ) -> BottomPane

调用图:调用 2 个内部函数(new, test_dummy);外部调用 1 个(new)。

tests::exec_request1894–1908 ↗
fn exec_request() -> ApprovalRequest

调用图:调用 1 个内部函数(new);外部调用 1 个(vec!)。

tests::DismissibleView::render1918–1918 ↗
fn render(&self, _area: Rect, _buf: &mut Buffer)
tests::DismissibleView::desired_height1920–1922 ↗
fn desired_height(&self, _width: u16) -> u16
tests::DismissibleView::is_complete1926–1928 ↗
fn is_complete(&self) -> bool
tests::DismissibleView::view_id1930–1932 ↗
fn view_id(&self) -> Option<&'static str>
tests::DismissibleView::dismiss_app_server_request1934–1944 ↗
fn dismiss_app_server_request(&mut self, request: &ResolvedAppServerRequest) -> bool
tests::CompletingView::render1954–1954 ↗
fn render(&self, _area: Rect, _buf: &mut Buffer)
tests::CompletingView::desired_height1956–1958 ↗
fn desired_height(&self, _width: u16) -> u16
tests::CompletingView::handle_key_event1962–1966 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)
tests::CompletingView::is_complete1968–1970 ↗
fn is_complete(&self) -> bool
tests::CompletingView::view_id1972–1974 ↗
fn view_id(&self) -> Option<&'static str>
tests::ctrl_c_on_modal_consumes_without_showing_quit_hint1978–1996 ↗
fn ctrl_c_on_modal_consumes_without_showing_quit_hint()

调用图:调用 4 个内部函数(with_defaults, new, new, test_dummy);外部调用 4 个(new, assert!, assert_eq!, exec_request)。

tests::ctrl_c_cancels_history_search_without_clearing_draft_or_showing_quit_hint1999–2021 ↗
fn ctrl_c_cancels_history_search_without_clearing_draft_or_showing_quit_hint()

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 5 个(Char, new, new, assert!, assert_eq!)。

tests::overlay_not_shown_above_approval_modal2026–2057 ↗
fn overlay_not_shown_above_approval_modal()

调用图:调用 4 个内部函数(with_defaults, new, new, test_dummy);外部调用 6 个(empty, new, new, new, assert!, exec_request)。

tests::approval_request_shows_immediately_without_recent_typing2060–2070 ↗
fn approval_request_shows_immediately_without_recent_typing()

调用图:调用 2 个内部函数(with_defaults, new);外部调用 4 个(assert!, assert_eq!, exec_request, test_pane)。

tests::approval_request_is_delayed_after_recent_typing2073–2095 ↗
fn approval_request_is_delayed_after_recent_typing()

调用图:调用 2 个内部函数(with_defaults, new);外部调用 6 个(from_millis, now, assert!, assert_eq!, exec_request, test_pane)。

tests::continued_typing_resets_delayed_approval_idle_deadline2098–2117 ↗
fn continued_typing_resets_delayed_approval_idle_deadline()

调用图:调用 2 个内部函数(with_defaults, new);外部调用 6 个(from_millis, now, assert!, assert_eq!, exec_request, test_pane)。

tests::typed_approval_shortcuts_during_delay_stay_in_composer2120–2140 ↗
fn typed_approval_shortcuts_during_delay_stay_in_composer()

调用图:调用 2 个内部函数(with_defaults, new);外部调用 7 个(now, Char, new, assert!, assert_eq!, exec_request, test_pane_with_disable_paste_burst)。

tests::delayed_approval_shortcut_works_after_idle_deadline2143–2169 ↗
fn delayed_approval_shortcut_works_after_idle_deadline()

作用:测试审批提示因为用户刚打完字而延迟显示时,等到空闲时间到了以后,按 y 仍然能批准命令。这样可以避免用户一边打字一边误触审批,但真正该审批时快捷键也不能失效。

数据流:测试先创建一个底部面板和事件接收器,记录“刚刚有输入活动”的时间,再塞入一个命令执行审批请求。它把时间推进到允许显示审批提示的空闲期限,然后模拟用户按下 y。最后从事件队列里找审批结果,确认结果从“待处理”变成了“接受”。

调用关系:这个测试由测试框架运行。它用 test_pane 建一个面板,用 exec_request 准备假审批请求,再通过 pre_draw_tick_at 和 handle_key_event 模拟真实界面的一次刷新和一次按键,最后检查面板是否向外发出了正确的 AppEvent。

调用图:调用 2 个内部函数(with_defaults, new);外部调用 6 个(now, Char, new, assert_eq!, exec_request, test_pane)。

tests::dismiss_app_server_request_prunes_delayed_approval2172–2190 ↗
fn dismiss_app_server_request_prunes_delayed_approval()

作用:测试一个还没真正显示出来的延迟审批请求,如果被服务端取消了,底部面板会把它从等待队列里删掉。否则用户稍后可能会看到一个已经无效的审批弹窗。

数据流:测试先创建面板,标记用户刚刚输入过文字,然后加入一个会延迟显示的审批请求。接着模拟服务端通知这个请求已经被取消。结果应该是:取消操作返回成功,延迟审批列表被清空;即使之后时间到了,也不会再出现任何弹窗。

调用关系:测试框架调用它。它围绕 dismiss_app_server_request 这个入口做验证,先通过 push_approval_request 放入请求,再用 pre_draw_tick_at 检查取消后不会被后续刷新重新推到界面上。

调用图:调用 2 个内部函数(with_defaults, new);外部调用 4 个(now, assert!, exec_request, test_pane)。

tests::dismiss_app_server_request_removes_matching_buried_view2193–2219 ↗
fn dismiss_app_server_request_removes_matching_buried_view()

作用:测试要取消的审批弹窗不在最上层、而是被别的窗口盖住时,也能被准确移除。这样可以防止底层残留一个已经过期的窗口。

数据流:测试先往面板里压入两个视图:底下那个绑定 request-1,上面那个只是普通顶层视图。然后发出取消 request-1 的请求。结果是底下匹配的视图被删掉,顶层视图还在,视图栈长度从 2 变成 1。

调用关系:它直接测试 view_stack 这类“窗口堆叠”行为。测试用 push_view 制造上下两层窗口,再让 dismiss_app_server_request 去堆里找匹配项,最后检查没有误删最上层窗口。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert!, assert_eq!, test_pane)。

tests::dismiss_app_server_request_returns_false_when_no_view_matches2222–2248 ↗
fn dismiss_app_server_request_returns_false_when_no_view_matches()

作用:测试服务端要求取消某个请求时,如果当前没有任何窗口对得上,函数会明确返回 false,并且不改动现有窗口。这样调用方能知道“没找到要取消的东西”。

数据流:测试创建两个视图,一个绑定了别的请求,另一个没有绑定请求。然后尝试取消 request-1。因为没有匹配项,返回值应该是 false,视图栈仍然保留两个视图,最上层还是原来的 second。

调用关系:这个测试补上 dismiss_app_server_request 的反面情况。它和移除匹配窗口的测试形成一组:一个证明能删对的,另一个证明找不到时不会乱删。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert!, assert_eq!, test_pane)。

tests::completing_top_view_preserves_underlying_view2251–2273 ↗
fn completing_top_view_preserves_underlying_view()

作用:测试最上层窗口完成并关闭时,不会把下面的窗口也一起关掉。窗口栈应该像一叠纸,只拿走最上面那张。

数据流:测试先压入一个底层视图,再压入一个按 Enter 会完成的顶层视图。随后模拟按 Enter。结果是顶层视图被移除,底层视图仍然留在栈里。

调用关系:测试框架运行它来确认 handle_key_event 对当前活动视图的收尾逻辑正确。它用 push_view 安排上下层关系,再让按键事件触发顶层视图完成。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, assert_eq!, test_pane)。

tests::composer_shown_after_denied_while_task_running2276–2341 ↗
fn composer_shown_after_denied_while_task_running()

作用:测试任务还在运行时,如果用户拒绝了一个审批弹窗,弹窗会消失,但“正在工作”的状态和输入框还应该显示。否则用户会以为界面卡住了,或者不知道还能继续输入。

数据流:测试创建真实的 BottomPane,启动任务运行状态,再打开一个审批弹窗。它模拟用户按 n 拒绝审批,然后检查弹窗栈为空。接着渲染一块终端区域,读取第一行和下面几行文字,确认上面有 Working,下面还能看到输入框占位文字 Ask Codex。

调用关系:它串起了 set_task_running、push_approval_request、handle_key_event 和 render。这个测试关注的是多个部件交替出现时的最终画面:审批层退场后,状态栏和输入框必须重新接上。

调用图:调用 4 个内部函数(with_defaults, new, new, test_dummy);外部调用 10 个(empty, from_millis, Char, new, new, new, new, assert!, sleep, exec_request)。

tests::status_indicator_visible_during_command_execution2344–2368 ↗
fn status_indicator_visible_during_command_execution()

作用:测试命令执行期间底部面板会显示“正在工作”的状态提示。用户需要这个提示来知道程序不是没反应,而是在执行任务。

数据流:测试新建面板,把任务状态设为运行中,然后把面板渲染到一个测试用缓冲区里。最后把缓冲区内容转成文本,确认里面包含“• Working”。

调用关系:它主要验证 set_task_running 对 render 输出的影响。测试框架调用它,Buffer 和 snapshot_buffer 只是用来读取终端画面里的文字。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 5 个(empty, new, new, assert!, snapshot_buffer)。

tests::status_and_composer_fill_height_without_bottom_padding2371–2399 ↗
fn status_and_composer_fill_height_without_bottom_padding()

作用:测试状态栏和输入框占满它们需要的高度时,底部不会多留一行空白。这个细节会影响终端界面的紧凑程度。

数据流:测试创建面板并设置任务运行中,然后询问面板在宽度 30 下想要多高。它用这个刚好够用的高度渲染画面,并用快照确认画面底部没有额外 padding,也就是没有无意义的留白。

调用关系:它把 desired_height 和 render 放在一起检查:面板自己说需要多少高度,实际画出来就应该正好用掉这些高度。assert_snapshot 用来保存和比较预期画面。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, new, assert!, assert_snapshot!)。

tests::status_only_snapshot2402–2422 ↗
fn status_only_snapshot()

作用:测试只有运行状态时,底部面板画出来的样子保持稳定。快照测试像给界面拍照片,之后代码改动如果让画面变了,就会被发现。

数据流:测试创建面板,设置任务运行中,按宽度 48 计算所需高度,然后渲染成文本快照。输出不是手动逐字检查,而是和保存的标准画面比较。

调用关系:它覆盖状态栏的基础渲染路径。set_task_running 提供状态,desired_height 决定画布大小,render_snapshot 负责把最终终端画面交给快照断言。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 3 个(new, new, assert_snapshot!)。

tests::unified_exec_summary_does_not_increase_height_when_status_visible2425–2451 ↗
fn unified_exec_summary_does_not_increase_height_when_status_visible()

作用:测试后台终端进程摘要出现时,如果状态栏已经可见,它不会额外撑高底部面板。也就是说,提示文字要合并进已有区域,而不是把界面往上挤。

数据流:测试先启动运行状态,记录当前所需高度。然后设置一个后台执行进程,比如 sleep 5,再次计算高度。结果高度必须不变;随后渲染画面,确认能看到“background terminal running · /ps to view”这样的摘要文字。

调用关系:它检查 set_unified_exec_processes 和状态栏显示之间的配合。desired_height 用来防止布局变高,render_snapshot 用来确认摘要仍然确实显示出来。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 6 个(new, new, assert!, assert_eq!, render_snapshot, vec!)。

tests::status_with_details_and_queued_messages_snapshot2454–2488 ↗
fn status_with_details_and_queued_messages_snapshot()

作用:测试运行状态带详细说明、同时还有排队消息时,底部面板的整体显示是否正确。用户既要知道当前在做什么,也要看到自己后续输入已经排队。

数据流:测试创建面板并启动任务,设置状态标题 Working 和两行详情,再设置一条待发送的后续问题。之后按宽度 48 计算高度并渲染,用快照确认状态详情和排队消息都在正确位置。

调用关系:它组合使用 update_status 和 set_pending_input_preview。这个测试不是只看一个部件,而是看状态区、详情区、排队消息区一起出现时有没有互相挤乱。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, new, assert_snapshot!, vec!)。

tests::queued_messages_visible_when_status_hidden_snapshot2491–2520 ↗
fn queued_messages_visible_when_status_hidden_snapshot()

作用:测试即使状态提示被隐藏,排队消息仍然能显示。这样用户不会因为状态栏收起,就看不到自己已经排队的输入。

数据流:测试先让任务进入运行中,再设置一条排队消息,随后主动隐藏状态提示。最后渲染画面并做快照比较,确认排队消息仍然保留在底部面板中。

调用关系:它验证 hide_status_indicator 不会顺手清掉 pending input preview。set_pending_input_preview 提供排队内容,render_snapshot 检查最终画面。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, new, assert_snapshot!, vec!)。

tests::status_and_queued_messages_snapshot2523–2551 ↗
fn status_and_queued_messages_snapshot()

作用:测试运行状态和排队消息同时显示时的标准画面。这个场景很常见:任务还没结束,用户又输入了下一句话。

数据流:测试创建面板,设置任务运行中,再设置一条排队的后续问题。它计算需要的高度,渲染并用快照确认状态提示和排队消息都正确显示。

调用关系:它是状态栏和排队输入预览的常规组合测试。和“隐藏状态仍显示排队消息”的测试一起,覆盖两种显示状态。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, new, assert_snapshot!, vec!)。

tests::remote_images_render_above_composer_text2554–2580 ↗
fn remote_images_render_above_composer_text()

作用:测试远程图片或 data 图片作为附件时,会显示在输入框文字上方。这样用户能先看到已附加的图片,再继续编辑文字。

数据流:测试给面板设置两个图片地址,一个普通网址,一个 data:image 开头的内嵌图片。它确认输入框文字仍为空,然后渲染画面,检查里面出现 [Image #1] 和 [Image #2] 两个图片占位提示。

调用关系:它围绕 set_remote_image_urls、composer_text、desired_height 和 render_snapshot 展开。重点是图片附件属于输入前的预览内容,不应该混进输入框文字里。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 6 个(new, new, assert!, assert_eq!, render_snapshot, vec!)。

tests::drain_pending_submission_state_clears_remote_image_urls2583–2603 ↗
fn drain_pending_submission_state_clears_remote_image_urls()

作用:测试提交状态被取走后,已附加的远程图片地址会被清空。否则下一次提交可能会误带上上一次的图片。

数据流:测试先设置一个远程图片地址,并确认列表长度是 1。然后调用 drain_pending_submission_state,表示把当前待提交内容取走。之后再查看图片地址列表,应该已经为空。

调用关系:它检查 set_remote_image_urls 和 drain_pending_submission_state 的衔接。这个测试保证提交动作像“打包并清空购物车”,图片附件不会残留。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, assert!, assert_eq!, vec!)。

tests::esc_with_skill_popup_does_not_interrupt_task2606–2651 ↗
fn esc_with_skill_popup_does_not_interrupt_task()

作用:测试任务运行中打开技能弹窗时,按 Esc 只关闭弹窗,不会中断任务。Esc 是个危险键,必须根据当前上下文判断含义。

数据流:测试创建带一个技能的面板,启动任务运行状态,然后输入 $ 触发技能弹窗。接着模拟按 Esc,并检查事件队列里没有 Interrupt 中断事件,同时确认弹窗已经关闭。

调用关系:它测试 handle_key_event 对 Esc 的优先级处理。composer 的 popup_active 表示当前有弹窗,所以 Esc 应先交给弹窗关闭逻辑,而不是发给后端去停止任务。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 3 个(new, assert!, vec!)。

tests::esc_with_slash_command_popup_does_not_interrupt_task2654–2686 ↗
fn esc_with_slash_command_popup_does_not_interrupt_task()

作用:测试任务运行中打开斜杠命令弹窗时,按 Esc 不会中断任务。斜杠命令弹窗是用户正在选命令,不代表想停止当前任务。

数据流:测试启动一个正在运行的任务,输入 / 触发命令弹窗,然后按 Esc。它遍历事件队列,确认没有 Interrupt;同时确认输入框里还保留着 /。

调用关系:它和技能弹窗测试类似,但覆盖的是 slash command,也就是以 / 开头的命令建议。handle_key_event 必须先处理输入框弹窗状态,再考虑是否中断任务。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, new, assert!, assert_eq!)。

tests::esc_with_agent_command_without_popup_does_not_interrupt_task2689–2722 ↗
fn esc_with_agent_command_without_popup_does_not_interrupt_task()

作用:测试用户正在输入 /agent 命令但弹窗已经消失时,按 Esc 也不该中断任务。因为这时用户仍在编辑一个本地命令草稿。

数据流:测试启动任务,输入 /agent 后带空格,使命令弹窗隐藏。然后按 Esc。结果事件队列里没有 Interrupt,中断没有发生;输入框文字仍然是 /agent 加空格。

调用关系:它补充了一个更细的上下文:没有弹窗不一定就能中断任务,还要看输入内容是不是特殊命令草稿。这个行为由 handle_key_event 判断。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 4 个(new, new, assert!, assert_eq!)。

tests::esc_release_after_dismissing_agent_picker_does_not_interrupt_task2725–2770 ↗
fn esc_release_after_dismissing_agent_picker_does_not_interrupt_task()

作用:测试按下 Esc 关闭代理选择器后,随后键盘释放 Esc 的事件不会被误当成第二次 Esc,从而中断任务。很多终端会分别发“按下”和“松开”事件,这里要避免误伤。

数据流:测试启动任务,打开一个选择视图。它先发送 Esc 按下事件来关闭选择器,再发送 Esc 松开事件。最后检查事件队列没有 Interrupt,并确认已经没有弹窗或弹出层。

调用关系:它检查 handle_key_event 对 KeyEventKind::Press 和 Release 的区分。show_selection_view 提供可关闭的选择器,后续 Esc release 必须被忽略或安全处理。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 5 个(default, new_with_kind, new, assert!, vec!)。

tests::esc_interrupts_running_task_when_no_popup2773–2795 ↗
fn esc_interrupts_running_task_when_no_popup()

作用:测试任务运行中没有任何弹窗、选择器或特殊输入状态时,按 Esc 会真正发出中断任务的请求。这是 Esc 的默认“停止当前回合”作用。

数据流:测试创建面板并设置任务运行中,然后直接按 Esc。事件接收器应该收到 AppEvent::CodexOp(Op::Interrupt),表示面板已经把用户的中断意图发给后端。

调用关系:这个测试是多个“有弹窗不应中断”测试的对照组。它证明 Esc 不是永远无效,而是在没有更高优先级的本地界面要处理时,才交给任务中断逻辑。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 3 个(new, new, assert!)。

tests::remapped_interrupt_turn_uses_configured_key_including_agent_drafts2798–2819 ↗
fn remapped_interrupt_turn_uses_configured_key_including_agent_drafts()

作用:测试如果用户把“中断任务”的快捷键从 Esc 改成 F12,那么即使正在编辑 /agent 草稿,也要按 F12 才会中断。这样自定义快捷键才真正生效。

数据流:测试创建面板,修改运行时快捷键表,把 chat.interrupt_turn 设成 F12。然后启动任务并输入 /agent。按 Esc 时事件队列应为空;按 F12 时才收到 Interrupt 事件。

调用关系:它通过 set_keymap_bindings 把快捷键配置塞进面板,再用 handle_key_event 验证实际按键路径。这个测试把快捷键系统和底部输入状态连接起来检查。

调用图:调用 2 个内部函数(new, defaults);外部调用 5 个(F, new, assert!, test_pane, vec!)。

tests::selection_view_esc_respects_remapped_list_cancel2822–2850 ↗
fn selection_view_esc_respects_remapped_list_cancel()

作用:测试列表取消键被改成 q 后,Esc 不再关闭选择列表,只有 q 才关闭。这样用户改快捷键后,选择器也遵守同一套规则。

数据流:测试修改快捷键表,把 list.cancel 设为 q。它打开一个选择视图,并给取消动作挂上一个会发送 OpenApprovalsPopup 的回调。按 Esc 后视图还在、没有事件;按 q 后视图关闭,并收到 OpenApprovalsPopup 事件。

调用关系:它测试 show_selection_view、set_keymap_bindings 和 handle_key_event 的配合。选择视图的取消逻辑不能写死 Esc,而要看当前键位配置。

调用图:调用 2 个内部函数(new, defaults);外部调用 7 个(new, default, Char, new, assert!, test_pane, vec!)。

tests::esc_routes_to_handle_key_event_when_requested2853–2909 ↗
fn esc_routes_to_handle_key_event_when_requested()

作用:测试某些弹窗如果明确要求自己处理 Esc,那么底部面板会把 Esc 当普通按键交给它,而不是调用默认的取消逻辑。这样特殊视图可以自定义 Esc 的意义。

数据流:测试定义一个临时视图,里面分别统计 on_ctrl_c 被调用几次、handle_key_event 被调用几次,并声明 prefer_esc_to_handle_key_event 为 true。把它压入面板后按 Esc。结果是 on_ctrl_c 次数为 0,handle_key_event 次数为 1。

调用关系:它围绕 BottomPaneView 这个视图接口验证分发规则。push_view 放入活动视图,handle_key_event 收到 Esc 后应尊重视图自己的 prefer_esc_to_handle_key_event 请求。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 7 个(new, new, new, clone, new, new, assert_eq!)。

tests::release_events_are_ignored_for_active_view2912–2963 ↗
fn release_events_are_ignored_for_active_view()

作用:测试键盘松开事件不会再传给活动视图处理。否则一次按键可能被处理两次,导致光标移动两格或菜单跳两项。

数据流:测试定义一个只记录 handle_key_event 调用次数的视图,并把它压入面板。随后发送 Down 的按下事件和 Down 的松开事件。最终调用次数应为 1,说明只有按下事件被处理。

调用关系:它检查 handle_key_event 对活动视图的事件过滤。KeyEventKind::Release 是终端输入里的“松开键”,底部面板应该在分发给视图前就把它挡掉。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 7 个(new, new, new_with_kind, clone, new, new, assert_eq!)。

tests::paste_completion_clears_stacked_views_and_restores_composer_input2966–3044 ↗
fn paste_completion_clears_stacked_views_and_restores_composer_input()

作用:测试粘贴内容让顶层弹窗完成后,整个弹窗流程会被清理干净,并且输入框恢复可用。否则用户粘贴后可能被卡在已经结束的弹窗层里。

数据流:测试先禁用输入框,再压入一个底层阻塞视图和一个顶层“收到粘贴就完成”的视图。调用 handle_paste 粘贴 hello 后,顶层视图标记完成,面板应清空整个视图栈。接着按 Down 不应再传给原来的底层视图,并且渲染区域里能拿到输入光标位置,说明输入框回来了。

调用关系:它把 handle_paste、视图完成检测、视图栈清理和输入框恢复串起来测。底层 BlockingView 的调用次数用来证明旧弹窗没有残留接收按键。

调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 10 个(new, new, new, default, clone, new, new, new, assert!, assert_eq!)。

tui/src/public_widgets/composer_input.rs源码 ↗
domain_logic界面输入期间,主要在主循环收到键盘、粘贴、刷新和渲染时活跃

这个文件像是给一台复杂机器装了一个简单面板。真正干活的是内部的 ChatComposer,也就是项目里原本给聊天底部输入框用的组件;ComposerInput 则把它包起来,只露出外部最常用的按钮和指示灯。外部代码可以创建输入框、喂给它键盘事件或粘贴内容、判断用户是否提交、计算高度、渲染到屏幕缓冲区里。它还会偷偷清掉内部发出来但外部不关心的应用事件,避免消息堆积。这里有一个重要设计:外部只看到 ComposerAction,比如“用户提交了一段文字”或“什么也没提交”,不用直接面对内部更复杂的 InputResult。这样其他 crate,也就是 Rust 里的其他代码包,可以安全复用这个输入框,而不用依赖整个聊天界面的实现。

函数细节15
ComposerInput::new36–48 ↗
fn new() -> Self

作用:创建一个新的可复用输入框,并给它准备好内部通信通道和默认提示文字。有人想在界面里放一个任务输入框时,通常就从这里开始。

数据流:进去时不需要参数 → 它新建一对内部消息通道,把发送端包装成 AppEventSender,再创建真正的 ChatComposer,并开启增强按键支持,让 Shift+Enter 这类组合键能按预期工作 → 出来的是一个 ComposerInput,里面已经带好输入框、发送端和接收端,可以马上接收键盘输入和渲染。

调用关系:这是整个组件的起点。Default 默认构造会走到它,测试里渲染输入字符时也会用它。它把底层的 ChatComposer 和应用事件通道组装好,后面的 input、handle_paste、render_ref 等方法都依赖这里创建出的 inner。

调用图:调用 2 个内部函数(new, new);被 2 处调用(new, composer_input_renders_typed_characters);外部调用 1 个(unbounded_channel)。

ComposerInput::is_empty51–53 ↗
fn is_empty(&self) -> bool

作用:判断当前输入框里有没有文字。外部可以用它来决定是否允许提交、是否显示空状态,或者是否需要清理输入。

数据流:进去的是当前 ComposerInput 自己 → 它直接询问内部 ChatComposer 现在是不是空的 → 出来一个 true 或 false,true 表示没有输入内容,false 表示里面有文字。

调用关系:它是一个很薄的查询口,只负责把问题转交给 inner.is_empty。它不会改动输入框,也不会触发事件,适合界面逻辑随时检查当前状态。

调用图:调用 1 个内部函数(is_empty)。

ComposerInput::clear56–59 ↗
fn clear(&mut self)

作用:清空输入框里的文字。通常在用户提交成功后,或者需要重置表单时使用。

数据流:进去的是一个可修改的 ComposerInput → 它把内部 ChatComposer 的文本内容设置成空字符串,并同时给相关附加信息传入空列表 → 之后输入框里的可见文字被清掉,但组件本身还可以继续使用。

调用关系:它直接调用内部的 set_text_content,相当于让 ChatComposer 忘掉当前文本。它不负责提交,也不处理键盘,只是一个明确的“擦黑板”动作。

调用图:调用 1 个内部函数(set_text_content);外部调用 2 个(new, new)。

ComposerInput::input62–69 ↗
fn input(&mut self, key: KeyEvent) -> ComposerAction

作用:把一次键盘按键交给输入框处理,并把复杂结果翻译成外部容易理解的动作。最重要的是,它能告诉外部:用户这次是不是按下了提交。

数据流:进去的是一个 KeyEvent,也就是一次键盘事件 → 它交给内部 ChatComposer 处理,内部可能改文字、移动光标、提交内容;如果内部结果是 Submitted,就取出提交文本包装成 ComposerAction::Submitted,否则返回 ComposerAction::None;最后它清掉内部产生但这里不需要处理的应用事件 → 出来的是一个高层动作,外部可以据此决定是否发送任务或重绘界面。

调用关系:这是主循环处理键盘输入时最常用的入口。它调用 handle_key_event 做真正的编辑工作,再调用 drain_app_events 收尾,避免内部事件留在队列里。它把内部的 InputResult 简化成 ComposerAction,降低外部使用难度。

调用图:调用 2 个内部函数(handle_key_event, drain_app_events);外部调用 1 个(Submitted)。

ComposerInput::handle_paste71–75 ↗
fn handle_paste(&mut self, pasted: String) -> bool

作用:处理用户粘贴进来的一大段文字。它让输入框使用内部已有的粘贴判断和合并策略,而不是把粘贴当成一堆普通按键乱处理。

数据流:进去的是一段 pasted 字符串 → 它把这段文字交给内部 ChatComposer,内部决定如何插入、是否启用粘贴突发检测;随后清空内部事件队列 → 出来一个布尔值,表示这次粘贴是否被输入框成功处理,同时输入框内容可能已经发生变化。

调用关系:它在终端收到粘贴事件时被调用。真正的粘贴规则在 inner.handle_paste 里,这个包装方法负责转交和清理后续 AppEvent。它和 flush_paste_burst_if_due 配合,用来让大段粘贴显示得更顺滑。

调用图:调用 2 个内部函数(handle_paste, drain_app_events)。

ComposerInput::set_hint_items79–85 ↗
fn set_hint_items(&mut self, items: Vec<(impl Into<String>, impl Into<String>)>)

作用:自定义输入框下面显示的操作提示。比如把底部提示改成“Enter 提交”“Esc 取消”,让用户知道能按什么键。

数据流:进去的是一组键名和说明文字的配对,每一项都可以转换成字符串 → 它把这些配对统一转换成 String,然后交给内部 ChatComposer 作为页脚提示覆盖项 → 之后渲染输入框时,底部会显示这些自定义提示。

调用关系:它服务于外部界面定制,不改输入文字。它调用 set_footer_hint_override,把默认提示换成调用方指定的内容。clear_hint_items 则负责撤销这个覆盖。

调用图:调用 1 个内部函数(set_footer_hint_override)。

ComposerInput::clear_hint_items88–90 ↗
fn clear_hint_items(&mut self)

作用:取消之前设置过的自定义底部提示,恢复输入框自己的默认提示。适合离开某个特殊模式后使用。

数据流:进去的是当前可修改的 ComposerInput → 它向内部 ChatComposer 传入 None,表示不再使用外部覆盖的提示项 → 之后输入框渲染时会回到默认提示。

调用关系:它和 set_hint_items 是一对。set_hint_items 负责换提示,clear_hint_items 负责恢复。实际修改仍由内部 set_footer_hint_override 完成。

调用图:调用 1 个内部函数(set_footer_hint_override)。

ComposerInput::desired_height93–95 ↗
fn desired_height(&self, width: u16) -> u16

作用:根据给定宽度,计算这个输入框希望占多少行。界面排版时需要它来避免文字被挤坏或显示不全。

数据流:进去的是宽度 width,单位是终端字符格 → 它询问内部 ChatComposer 在这个宽度下需要几行来显示当前内容和提示 → 出来一个行数 u16,供布局系统安排空间。

调用关系:它通常在渲染前的布局阶段使用。它不自己计算换行细节,而是把问题交给 inner.desired_height,因为内部组件最清楚当前文字和样式需要多高。

调用图:调用 1 个内部函数(desired_height)。

ComposerInput::cursor_pos98–100 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:计算光标应该显示在屏幕上的哪个位置。没有它,终端界面可能不知道该把闪烁光标放到哪一格。

数据流:进去的是一个 Rect,也就是输入框在屏幕上的矩形区域 → 它让内部 ChatComposer 根据当前文本、光标位置和区域大小计算坐标 → 出来可能是一个坐标,也可能是 None,None 表示当前不需要或无法显示光标。

调用关系:它在界面准备显示光标时使用。它把区域信息交给 inner.cursor_pos,和 render_ref 使用的区域应该保持一致,这样光标位置才会和画出来的文字对得上。

调用图:调用 1 个内部函数(cursor_pos)。

ComposerInput::render_ref103–105 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

作用:把输入框画到终端界面的缓冲区里。缓冲区可以理解成一张临时画布,先画好,再统一显示到屏幕。

数据流:进去的是绘制区域 area 和可修改的 Buffer → 它调用内部 ChatComposer 的 render,把边框、文字、提示等内容写进这块缓冲区 → 出来没有单独返回值,但 Buffer 被改写,之后终端就能显示这个输入框。

调用关系:它在每次界面重绘时被调用。实际怎么画由 inner.render 完成,这个方法只是公开一个稳定、简单的渲染入口,让外部不用直接接触内部 ChatComposer。

调用图:调用 1 个内部函数(render)。

ComposerInput::is_in_paste_burst108–110 ↗
fn is_in_paste_burst(&self) -> bool

作用:判断当前是否正处在“粘贴突发”状态。粘贴突发指的是终端短时间内收到大量字符,通常是用户粘贴了一大段文本,而不是一个字一个字敲的。

数据流:进去的是当前 ComposerInput → 它询问内部 ChatComposer 是否正在识别和缓冲一段快速粘贴输入 → 出来一个布尔值,true 表示仍在粘贴突发处理中,false 表示没有。

调用关系:它常被外部刷新调度逻辑使用。如果返回 true,外部可能会安排稍后再 flush,也就是把暂存内容刷出来。真正状态保存在 inner 里。

调用图:调用 1 个内部函数(is_in_paste_burst)。

ComposerInput::flush_paste_burst_if_due114–118 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

作用:在粘贴突发等待时间到了之后,把暂存的粘贴内容刷进输入框。这样大段粘贴不会因为逐字符处理而显得卡顿或乱跳。

数据流:进去的是一个可修改的 ComposerInput → 它让内部 ChatComposer 检查是否已经到了该刷新的时间;如果到了,就更新输入文本;随后清掉内部事件队列 → 出来一个布尔值,true 表示文字发生变化、界面值得重画,false 表示这次不用动。

调用关系:它通常由定时刷新或主循环调用,和 handle_paste、is_in_paste_burst、recommended_flush_delay 配合使用。它把实际判断交给 inner.flush_paste_burst_if_due,自己负责把产生的 AppEvent 清理掉。

调用图:调用 2 个内部函数(flush_paste_burst_if_due, drain_app_events)。

ComposerInput::drain_app_events126–128 ↗
fn drain_app_events(&mut self)

作用:清空内部事件接收队列里已经产生但这个公开包装层不打算处理的事件。这样可以避免无用消息越积越多。

数据流:进去的是一个可修改的 ComposerInput → 它不断尝试从内部接收端 rx 里取事件,只要还能取到就丢掉 → 出来没有返回值,但接收队列被尽量清空。

调用关系:它是 input、handle_paste、flush_paste_burst_if_due 的收尾动作。内部 ChatComposer 有时会发 AppEvent 给完整应用使用,但这个简化组件不暴露那些事件,所以这里把它们排空,保持包装层干净。

调用图:被 3 处调用(flush_paste_burst_if_due, handle_paste, input);外部调用 1 个(try_recv)。

ComposerInput::default132–134 ↗
fn default() -> Self

作用:提供 Rust 的默认创建方式,让别人可以用通用的 Default 习惯来创建 ComposerInput。它的效果和直接调用 ComposerInput::new 一样。

数据流:进去时没有参数 → 它调用 new 创建完整的输入框,包括内部 ChatComposer 和事件通道 → 出来一个默认配置好的 ComposerInput。

调用关系:这是标准接口的适配层。需要泛型默认值、测试默认初始化,或想写更简洁代码的地方会用它;真正的组装工作仍交给 ComposerInput::new。

调用图:外部调用 1 个(new)。

Composer 文本引擎和输入状态

此组构建可复用的文本编辑基础,以及聊天 composer 用来管理草稿、粘贴行为、附件和页脚/弹窗展示的底层状态组件。

tui/src/bottom_pane/paste_burst.rs源码 ↗
domain_logicrequest handling / main loop

这个文件实现了一个叫 PasteBurst 的“小侦探”。它不直接改输入框,只观察字符到来的速度:如果几个普通字符在极短时间内连续出现,就很像粘贴;如果只是一个字符,就先短暂藏一下,过了时间还没有后续字符,再当普通打字放出来。这样可以避免界面先显示一个字符、马上又改成粘贴造成闪烁。它还会在粘贴刚发生时短暂保护 Enter 键,让 Enter 变成换行,而不是提交消息。遇到已经先插入了一点文字、后来才发现像粘贴的情况,它还能告诉调用者应该从光标前“追回”多少文字,放回粘贴缓冲区。可以把它想成收银台旁边的判断员:看人是慢慢递零钱,还是一下倒出一把硬币,然后决定按普通输入还是整段粘贴处理。

函数细节25
PasteBurst::on_plain_char218–248 ↗
fn on_plain_char(&mut self, ch: char, now: Instant) -> CharDecision

作用:处理一个普通 ASCII 字符,并决定它应该先藏起来、加入粘贴缓冲,还是触发“开始粘贴”的判断。这里的普通字符指没有 Ctrl、Alt 这类修饰键的字符。

数据流:进去一个字符和当前时间 → 先记录这次字符到来的时间和连续快速字符数量 → 如果已经在粘贴中,就要求追加到缓冲;如果前一个字符被暂存且这次来得很快,就把两者组成粘贴;如果连续字符够多,就提示调用者追回之前已经插入的字符;否则先暂存当前字符 → 出来一个 CharDecision,告诉调用者下一步怎么做。

调用关系:它是 ASCII 输入路径的核心入口,会把计时工作交给 PasteBurst::note_plain_char,并用 duration_since 比较两次输入间隔;返回的决定由外层输入框逻辑执行,自己不直接改界面。

调用图:调用 1 个内部函数(note_plain_char);外部调用 1 个(duration_since)。

PasteBurst::on_plain_char_no_hold256–271 ↗
fn on_plain_char_no_hold(&mut self, now: Instant) -> Option<CharDecision>

作用:处理不适合暂存的普通字符,比如中文输入法或非 ASCII 字符。它不会把第一个字符藏起来,因为那样用户会感觉字被吞了。

数据流:进去当前时间,不接收具体字符内容 → 记录这次普通字符的时间 → 如果已经在粘贴中,就返回追加到缓冲;如果连续快速字符数量达到阈值,就返回开始缓冲并提示可追回前面的字符;否则返回 None,表示正常插入 → 它只给判断,不改输入框。

调用关系:它调用 PasteBurst::note_plain_char 更新快速输入计数;调用图显示 handle_key_event_at 会在处理按键时使用它,特别是非 ASCII 或输入法场景。

调用图:调用 1 个内部函数(note_plain_char);被 1 处调用(handle_key_event_at)。

PasteBurst::note_plain_char273–282 ↗
fn note_plain_char(&mut self, now: Instant)

作用:记录一次普通字符输入,并更新“这些字符是不是来得很密集”的计数。它是判断粘贴的计时基础。

数据流:进去当前时间 → 和上一次普通字符时间比较,如果间隔很短就把连续计数加一,否则从一重新开始 → 最后把当前时间保存为最近一次普通字符时间。

调用关系:PasteBurst::on_plain_char 和 PasteBurst::on_plain_char_no_hold 都先调用它;它内部用 duration_since 算时间差,为后面的粘贴判断提供依据。

调用图:被 2 处调用(on_plain_char, on_plain_char_no_hold);外部调用 1 个(duration_since)。

PasteBurst::flush_if_due293–317 ↗
fn flush_if_due(&mut self, now: Instant) -> FlushResult

作用:在界面定时刷新时检查:暂存的字符或粘贴缓冲是不是该放出来了。它决定输出单个普通字符、整段粘贴,还是暂时什么都不做。

数据流:进去当前时间 → 根据现在是否处在粘贴相关状态选择超时时间 → 如果超时且有粘贴缓冲,就清掉活跃标记并取出整段文本作为 Paste;如果只是暂存了第一个字符,就取出它作为 Typed;如果还没到时间或没东西,就返回 None → 会改变内部缓冲和暂存字符状态。

调用关系:它调用 PasteBurst::is_active_internal 判断内部是否有缓冲,又用 take 把字符串或暂存值安全搬出来;外层 UI tick 会根据 FlushResult 决定是普通插入还是走粘贴处理。

调用图:调用 1 个内部函数(is_active_internal);外部调用 3 个(take, Paste, Typed)。

PasteBurst::append_newline_if_active324–332 ↗
fn append_newline_if_active(&mut self, now: Instant) -> bool

作用:如果当前正处在粘贴上下文里,就把 Enter 当成换行塞进粘贴内容,而不是提交消息。

数据流:进去当前时间 → 先检查是否处在粘贴相关状态 → 如果是,就把 '\n' 加进缓冲,并延长 Enter 保护窗口,返回 true;如果不是,什么也不改,返回 false。

调用关系:它调用 PasteBurst::is_active 判断是否应当接管 Enter;通常会被按键处理流程在遇到 Enter 时用来避免多行粘贴被提前发送。

调用图:调用 1 个内部函数(is_active)。

PasteBurst::newline_should_insert_instead_of_submit335–338 ↗
fn newline_should_insert_instead_of_submit(&self, now: Instant) -> bool

作用:判断现在按 Enter 应该换行,还是应该提交消息。它主要保护粘贴中的多行文本。

数据流:进去当前时间 → 查看是否还在短暂的 Enter 保护窗口内,也查看是否仍处于粘贴状态 → 只要满足其中之一,就输出 true,表示 Enter 应插入换行;否则输出 false。

调用关系:它调用 PasteBurst::is_active;PasteBurst::direct_insert_newline_should_insert 会在更直接插入字符的路径里复用这个判断。

调用图:调用 1 个内部函数(is_active);被 1 处调用(direct_insert_newline_should_insert)。

PasteBurst::direct_insert_newline_should_insert341–346 ↗
fn direct_insert_newline_should_insert(&self, now: Instant) -> bool

作用:给“直接插入字符”的调用者判断 Enter 的用途:此刻更像粘贴时,Enter 就应该变成换行。

数据流:进去当前时间 → 先复用 newline_should_insert_instead_of_submit 的判断 → 如果还不够,再看最近一次普通字符是不是刚刚发生,若非常接近也认为像粘贴 → 输出 true 表示插入换行,false 表示可以提交。

调用关系:它调用 PasteBurst::newline_should_insert_instead_of_submit;调用图显示 handle_key_event_at 会在处理 Enter 时使用它。

调用图:调用 1 个内部函数(newline_should_insert_instead_of_submit);被 1 处调用(handle_key_event_at)。

PasteBurst::extend_window349–351 ↗
fn extend_window(&mut self, now: Instant)

作用:把 Enter 保护窗口再往后延一点。这样粘贴流里稍晚到达的 Enter 仍会被当成换行。

数据流:进去当前时间 → 用当前时间加上固定保护时长 → 更新 burst_window_until → 没有返回值,但改变了内部的保护截止时间。

调用关系:调用图显示 handle_key_event_at 会用它;它常配合快速字符或 Enter 事件,让粘贴识别窗口保持活着。

调用图:被 1 处调用(handle_key_event_at)。

PasteBurst::begin_with_retro_grabbed354–360 ↗
fn begin_with_retro_grabbed(&mut self, grabbed: String, now: Instant)

作用:用已经从输入框里追回来的文字开始一个粘贴缓冲。适合“刚才以为是打字,后来发现其实像粘贴”的情况。

数据流:进去追回来的字符串和当前时间 → 如果字符串不空,就放进内部缓冲 → 标记粘贴缓冲为活跃,并设置 Enter 保护窗口 → 没有返回值,但内部状态变成正在收集粘贴。

调用关系:PasteBurst::decide_begin_buffer 在确认前缀像粘贴后会调用它;它不负责从界面删除文字,只负责接住调用者已经决定追回的内容。

调用图:被 1 处调用(decide_begin_buffer)。

PasteBurst::append_char_to_buffer363–366 ↗
fn append_char_to_buffer(&mut self, ch: char, now: Instant)

作用:把当前字符追加进粘贴缓冲,并顺手延长 Enter 保护窗口。

数据流:进去一个字符和当前时间 → 把字符 push 到内部 buffer → 把保护窗口更新到当前时间之后的一小段 → 没有返回值,内部缓冲变长。

调用关系:PasteBurst::try_append_char_if_active 会调用它;外层在收到 BufferAppend 或 BeginBufferFromPending 这类决定后,也会用它把当前字符正式纳入粘贴内容。

调用图:被 1 处调用(try_append_char_if_active)。

PasteBurst::try_append_char_if_active371–378 ↗
fn try_append_char_if_active(&mut self, ch: char, now: Instant) -> bool

作用:尝试把字符加进已有粘贴缓冲,但只有确实已经在粘贴上下文里才会加。它避免普通打字被误塞进缓冲。

数据流:进去一个字符和当前时间 → 检查 active 标记或 buffer 是否非空 → 如果是,就调用 append_char_to_buffer 并返回 true;如果不是,什么也不改并返回 false。

调用关系:它把真正追加字符的活交给 PasteBurst::append_char_to_buffer;适合外层代码不确定当前是否仍在粘贴时做一次安全尝试。

调用图:调用 1 个内部函数(append_char_to_buffer)。

PasteBurst::decide_begin_buffer391–411 ↗
fn decide_begin_buffer(
        &mut self,
        now: Instant,
        before: &str,
        retro_chars: usize,
    ) -> Option<RetroGrab>

作用:判断要不要把光标前已经插入的一小段文字追回来,作为粘贴开头。它用来修正“先当打字插入、后发现像粘贴”的误差。

数据流:进去当前时间、光标前的文本 before、要追回的字符数 → 先用 retro_start_index 把字符数换成 UTF-8 字节位置 → 截出要追回的字符串 → 如果里面有空白字符,或长度至少 16 个字符,就认为像粘贴,调用 begin_with_retro_grabbed 开始缓冲,并返回 RetroGrab;否则返回 None,不改界面文字。

调用关系:它调用 retro_start_index 处理多字节字符的安全切片,又调用 PasteBurst::begin_with_retro_grabbed 开启缓冲;外层调用者拿到 RetroGrab 后才负责从输入框里删除那段文字。

调用图:调用 2 个内部函数(begin_with_retro_grabbed, retro_start_index)。

PasteBurst::flush_before_modified_input414–424 ↗
fn flush_before_modified_input(&mut self) -> Option<String>

作用:在处理方向键、Ctrl/Alt 快捷键等非普通字符前,先把当前粘贴相关内容吐出来,避免状态卡住或串到下一次输入。

数据流:进去没有参数 → 如果当前不活跃,直接返回 None → 如果活跃,就关闭 active,取出 buffer;如果还有暂存的第一个字符,也追加到输出末尾 → 返回 Some(String),让调用者按粘贴或文本内容处理。

调用关系:它调用 PasteBurst::is_active 判断是否有东西要冲刷,并用 take 取走缓冲;通常由按键处理流程在进入非字符输入前调用。

调用图:调用 1 个内部函数(is_active);外部调用 1 个(take)。

PasteBurst::clear_window_after_non_char430–436 ↗
fn clear_window_after_non_char(&mut self)

作用:清掉粘贴识别的计时窗口,让下一次打字不要被错误地接到上一段快速输入后面。

数据流:进去没有参数 → 把连续计数、最近字符时间、Enter 保护窗口、活跃标记和暂存字符都清空 → 不返回值;注意它不负责输出 buffer,调用者应该先 flush。

调用关系:它通常跟 flush_before_modified_input 搭配:先把内容吐出来,再清掉分类窗口;这样方向键或快捷键之后的新字符会重新开始判断。

PasteBurst::is_active441–443 ↗
fn is_active(&self) -> bool

作用:告诉外界:现在是否处在任何粘贴识别相关的临时状态里。包括正在缓冲、缓冲里还有内容,或者第一个字符正被短暂暂存。

数据流:进去没有参数 → 先调用 is_active_internal 看内部缓冲状态,再检查 pending_first_char 是否存在 → 输出布尔值 true 或 false。

调用关系:它被 PasteBurst::append_newline_if_active、PasteBurst::flush_before_modified_input 和 PasteBurst::newline_should_insert_instead_of_submit 使用,是外层判断“是否还在粘贴上下文”的统一入口。

调用图:调用 1 个内部函数(is_active_internal);被 3 处调用(append_newline_if_active, flush_before_modified_input, newline_should_insert_instead_of_submit)。

PasteBurst::is_active_internal445–447 ↗
fn is_active_internal(&self) -> bool

作用:只检查真正的粘贴缓冲状态,不把“暂存第一个字符”算进去。它是给内部更精细判断用的小工具。

数据流:进去没有参数 → 查看 active 是否为 true,或 buffer 是否非空 → 输出布尔值。

调用关系:PasteBurst::flush_if_due 用它决定采用粘贴缓冲超时还是普通首字符超时;PasteBurst::is_active 也调用它,再额外考虑暂存字符。

调用图:被 2 处调用(flush_if_due, is_active)。

PasteBurst::clear_after_explicit_paste449–456 ↗
fn clear_after_explicit_paste(&mut self)

作用:在程序明确收到一次真正的粘贴事件后,清空这个“猜粘贴”的状态机。这样旧的猜测不会影响新输入。

数据流:进去没有参数 → 清掉最近字符时间、连续计数、Enter 保护窗口、active、buffer 和 pending_first_char → 没有返回值,整个 PasteBurst 回到干净状态。

调用关系:调用图显示 handle_key_event_at 和 handle_paste 会调用它;它用于显式粘贴路径结束后做收尾,避免和猜测式粘贴检测互相干扰。

调用图:被 2 处调用(handle_key_event_at, handle_paste)。

retro_start_index459–469 ↗
fn retro_start_index(before: &str, retro_chars: usize) -> usize

作用:把“从末尾往前数几个字符”转换成字符串里的字节下标。Rust 字符串用 UTF-8,中文、emoji 这类字符可能占多个字节,所以不能直接按字节乱切。

数据流:进去一段字符串 before 和要往回数的字符数 retro_chars → 如果数量是 0,就返回字符串末尾 → 否则从后往前按字符找位置 → 找到就返回对应字节下标,找不到就返回 0。

调用关系:PasteBurst::decide_begin_buffer 调用它来安全计算追回文本的起点;它保证后续 before[start_byte..] 尽量落在合法字符边界上。

调用图:被 1 处调用(decide_begin_buffer)。

tests::ascii_first_char_is_held_then_flushes_as_typed479–490 ↗
fn ascii_first_char_is_held_then_flushes_as_typed()

作用:测试一个普通 ASCII 字符不会马上显示,而是先被短暂暂存;如果后面没有快速字符跟上,它最终会像正常打字一样输出。

数据流:进去没有外部输入 → 创建默认 PasteBurst 和当前时间 → 输入字符 'a',检查返回 RetainFirstChar → 把时间推进到推荐等待时间之后 → 调用 flush_if_due,确认得到 Typed('a'),并确认状态不再活跃。

调用关系:它调用 PasteBurst::recommended_flush_delay、now、default 和断言宏;它验证 recommended_flush_delay 与 flush_if_due 的配合是否能让单字符正常落地。

调用图:调用 1 个内部函数(recommended_flush_delay);外部调用 4 个(from_millis, now, assert!, default)。

tests::ascii_two_fast_chars_start_buffer_from_pending_and_flush_as_paste495–515 ↗
fn ascii_two_fast_chars_start_buffer_from_pending_and_flush_as_paste()

作用:测试两个 ASCII 字符快速连续到达时,会被当成一次粘贴,而不是先显示第一个再改判。

数据流:进去没有外部输入 → 创建 PasteBurst → 输入 'a' 后确认它被暂存 → 1 毫秒后输入 'b',确认开始从暂存字符建缓冲 → 把 'b' 追加进缓冲 → 时间越过活跃缓冲超时 → flush_if_due 应返回 Paste("ab")。

调用关系:它调用 PasteBurst::recommended_active_flush_delay、from_millis、now、default 和断言宏;它覆盖 on_plain_char、append_char_to_buffer、flush_if_due 这条典型快速粘贴路径。

调用图:调用 1 个内部函数(recommended_active_flush_delay);外部调用 4 个(from_millis, now, assert!, default)。

tests::flush_before_modified_input_includes_pending_first_char520–530 ↗
fn flush_before_modified_input_includes_pending_first_char()

作用:测试在处理非普通输入前,连那个还没显示的暂存首字符也会被吐出来,不会丢字。

数据流:进去没有外部输入 → 创建 PasteBurst → 输入 'a' 并确认被 RetainFirstChar 暂存 → 调用 flush_before_modified_input → 期待返回 Some("a"),并确认状态不活跃。

调用关系:它调用 now、default、assert 和 assert_eq;它专门保护 flush_before_modified_input 的收尾行为,避免快捷键或方向键前吞掉用户输入。

调用图:外部调用 4 个(now, assert!, assert_eq!, default)。

tests::decide_begin_buffer_only_triggers_for_pastey_prefixes535–552 ↗
fn decide_begin_buffer_only_triggers_for_pastey_prefixes()

作用:测试追回已插入文字时,只有看起来像粘贴的前缀才会触发缓冲,短短的普通词不会被误判。

数据流:进去没有外部输入 → 创建 PasteBurst 和当前时间 → 先用 "ab" 尝试追回 2 个字符,期待 None 且状态不活跃 → 再用 "a b" 尝试追回,因包含空白而被认为像粘贴 → 检查追回起点和追回内容,并确认状态活跃。

调用关系:它调用 now、default、assert 和 assert_eq;它验证 decide_begin_buffer 与 retro_start_index、begin_with_retro_grabbed 的组合不会太激进。

调用图:外部调用 4 个(now, assert!, assert_eq!, default)。

tests::newline_suppression_window_outlives_buffer_flush557–579 ↗
fn newline_suppression_window_outlives_buffer_flush()

作用:测试粘贴缓冲吐出以后,Enter 保护窗口还会短暂存在。这样稍晚到的 Enter 仍会被当作换行,而不是立刻提交消息。

数据流:进去没有外部输入 → 模拟 'a'、'b' 快速形成粘贴 → 等待活跃缓冲超时并确认 flush_if_due 返回 Paste("ab") → 虽然状态不活跃,仍检查当前时间下 Enter 应该插入换行 → 再把时间推进超过保护窗口,确认 Enter 不再被保护。

调用关系:它调用 PasteBurst::recommended_active_flush_delay、from_millis、now、default 和断言宏;它保护 newline_should_insert_instead_of_submit 与 burst_window_until 的关键行为。

调用图:调用 1 个内部函数(recommended_active_flush_delay);外部调用 4 个(from_millis, now, assert!, default)。

tui/src/bottom_pane/textarea/vim.rs源码 ↗
domain_logicrequest handling

这个文件处理的是“光标附近哪一段文字算一个对象”。在 Vim 里,用户可以说“删除这个词”“复制括号里面”“修改引号里面”,这里的“词、括号、引号里的内容”就叫文本对象。文件先定义了普通模式、插入模式、删除/复制/修改这些操作,以及“里面”和“连外壳一起”两种范围。真正干活时,它会把按键翻译成具体对象,再根据当前光标位置,在输入框文本里找出对应的文字范围。找词时,它会区分普通小词和用空白分隔的大词;找括号时,它用栈像配对袜子一样找最近的一对括号;找引号时,它只看当前行,并跳过被反斜杠转义的引号。它还会避开特殊元素区域,避免把界面里嵌入的非普通文字当成用户可编辑文本。

函数细节17
TextArea::vim_text_object_scope_for_event64–83 ↗
fn vim_text_object_scope_for_event(
        &self,
        event: KeyEvent,
    ) -> Option<VimTextObjectScope>

作用:把一次按键判断成 Vim 文本对象的范围选择:是选“里面”,还是选“周围连带外面的符号或空白”。用户按下类似 i 或 a 这类键时,会用它来决定后面怎么取范围。

数据流:进去的是一个键盘事件,以及输入框里配置好的快捷键表;它逐个检查这个键是不是“选里面”或“选周围”;出来的是 Inner、Around,或者什么都不是的 None,不会修改文本。

调用关系:它通常在 Vim 操作已经开始、正在等待用户指定文本对象范围时被调用。它只负责认出范围类型,不继续找具体文字;后续会再配合 TextArea::vim_text_object_for_event 和 TextArea::text_object_range 算出真正的位置。

TextArea::vim_text_object_for_event85–111 ↗
fn vim_text_object_for_event(&self, event: KeyEvent) -> Option<VimTextObject>

作用:把一次按键判断成具体的 Vim 文本对象,比如词、括号、方括号、花括号、双引号、单引号或反引号。这样输入框才知道用户想操作哪一类文字块。

数据流:进去的是键盘事件和文本对象快捷键表;它按顺序检查这个键对应哪种对象;出来的是一个 VimTextObject,或者 None,表示这个键不是文本对象键。

调用关系:它通常接在范围判断之后使用:先知道用户要“里面”还是“周围”,再用它知道对象是什么。它本身不计算范围,真正找文字位置的工作交给 TextArea::text_object_range。

TextArea::text_object_range113–128 ↗
fn text_object_range(
        &self,
        object: VimTextObject,
        scope: VimTextObjectScope,
    ) -> Option<Range<usize>>

作用:根据“对象类型”和“里面/周围”这两个选择,算出当前光标附近应该被操作的文字范围。删除、复制、修改这类 Vim 操作最终都需要这个范围。

数据流:进去的是对象类型,比如词或括号,以及范围类型,比如只要里面还是连外壳一起;它按对象分流:词交给 TextArea::word_text_object_range,括号交给 TextArea::paired_text_object_range,引号交给 TextArea::quoted_text_object_range;出来的是一段文字的位置范围,找不到就返回 None。

调用关系:它是文本对象查找的总入口。上游的按键处理先把按键翻译成对象和范围,然后调用它;它再把具体查找交给更专门的小函数。

调用图:调用 3 个内部函数(paired_text_object_range, quoted_text_object_range, word_text_object_range)。

TextArea::word_text_object_range130–144 ↗
fn word_text_object_range(
        &self,
        scope: VimTextObjectScope,
        big_word: bool,
    ) -> Option<Range<usize>>

作用:专门计算“词”这个文本对象的范围。它支持小词和大词,也支持只选词本身或连旁边空白一起选。

数据流:进去的是范围类型和是否为大词的标记;它先调用 TextArea::big_word_range_at_cursor 或 TextArea::small_word_range_at_cursor 找到光标所在的词;如果用户要 Around,它再调用 TextArea::expand_word_around 把旁边空白也带上;出来的是最终词范围,或者 None。

调用关系:它由 TextArea::text_object_range 在对象是 Word 或 BigWord 时调用。它自己不关心按键,只负责把“词对象”变成实际文字范围。

调用图:调用 3 个内部函数(big_word_range_at_cursor, expand_word_around, small_word_range_at_cursor);被 1 处调用(text_object_range)。

TextArea::big_word_range_at_cursor146–150 ↗
fn big_word_range_at_cursor(&self) -> Option<Range<usize>>

作用:找出光标所在的“大词”。这里的大词意思很简单:只要不是空白,连续的一串都算一个词。

数据流:进去的是当前输入框文本和光标位置;它先调用 TextArea::non_ws_runs 找出所有非空白片段,再挑出光标落在里面或刚好停在结尾的那一段;出来的是这段大词范围,或者 None。

调用关系:它只被 TextArea::word_text_object_range 调用,用在用户选择 BigWord 的时候。它把“按空白切块”的粗粒度找词工作单独拿出来。

调用图:调用 1 个内部函数(non_ws_runs);被 1 处调用(word_text_object_range)。

TextArea::small_word_range_at_cursor152–171 ↗
fn small_word_range_at_cursor(&self) -> Option<Range<usize>>

作用:找出光标所在的“小词”。小词比大词更细,会把一串非空白内容再按单词片段拆开,比如标点和字母可能不是同一个片段。

数据流:进去的是当前文本和光标位置;它先用 TextArea::non_ws_runs 找非空白大段,再用外部的 split_word_pieces 把这一段拆成更小的词片;它用 TextArea::cursor_overlaps_range 和 TextArea::cursor_is_at_range_end 判断光标在哪;出来的是最合适的小词范围,或者 None。

调用关系:它由 TextArea::word_text_object_range 在普通 Word 场景下调用。它依赖 split_word_pieces 做细拆分,自己负责把拆出来的小片段和光标位置对上。

调用图:调用 3 个内部函数(cursor_is_at_range_end, cursor_overlaps_range, non_ws_runs);被 1 处调用(word_text_object_range);外部调用 1 个(split_word_pieces)。

TextArea::non_ws_runs173–189 ↗
fn non_ws_runs(&self) -> Vec<Range<usize>>

作用:把整段输入文字切成一段段“不是空白”的连续区域。它是找词的基础步骤,相当于先把句子按空格粗略切开。

数据流:进去的是输入框里的完整文本;它从头扫描每个字符,遇到空白就结束当前片段,遇到非空白就开始或继续片段;出来的是多个位置范围组成的列表,不改动文本。

调用关系:它被 TextArea::big_word_range_at_cursor 和 TextArea::small_word_range_at_cursor 调用。前者直接把这些片段当大词,后者还会继续细分。

调用图:被 2 处调用(big_word_range_at_cursor, small_word_range_at_cursor);外部调用 1 个(new)。

TextArea::cursor_overlaps_range191–193 ↗
fn cursor_overlaps_range(&self, range: &Range<usize>) -> bool

作用:判断光标是不是落在某个文字范围里面。这个小判断能避免后面的找词逻辑反复写同样的边界判断。

数据流:进去的是一个文字范围和当前光标位置;它检查范围起点是否不超过光标、光标是否还没到范围终点;出来的是 true 或 false,不产生副作用。

调用关系:它被 TextArea::small_word_range_at_cursor 调用,用来确认光标是否在某个非空白段或小词片段内部。

调用图:被 1 处调用(small_word_range_at_cursor)。

TextArea::cursor_is_at_range_end195–197 ↗
fn cursor_is_at_range_end(&self, range: &Range<usize>) -> bool

作用:判断光标是不是刚好停在某段文字的末尾。这样用户把光标放在词尾后面时,仍然能把前面的词识别出来。

数据流:进去的是一个文字范围和当前光标位置;它确认这个范围不是空的,并且光标正好等于范围结尾;出来的是 true 或 false,不改动任何内容。

调用关系:它被 TextArea::small_word_range_at_cursor 调用,补上“光标在词尾后面”这种常见编辑位置。

调用图:被 1 处调用(small_word_range_at_cursor)。

TextArea::expand_word_around199–205 ↗
fn expand_word_around(&self, inner: Range<usize>) -> Range<usize>

作用:把“词本身”的范围扩成 Vim 里 Around 的范围,也就是尽量连旁边空白一起选中。这样删除一个词时,常常不会留下多余空格。

数据流:进去的是已经找到的词范围;它先调用 TextArea::following_whitespace_end 看词后面有没有空白,有就把后面的空白带上;如果后面没有,就调用 TextArea::preceding_whitespace_start 带上前面的空白;出来的是扩展后的范围。

调用关系:它由 TextArea::word_text_object_range 在用户选择 Around 时调用。它不负责找词,只负责把已找到的词范围变得更符合 Vim 的使用习惯。

调用图:调用 2 个内部函数(following_whitespace_end, preceding_whitespace_start);被 1 处调用(word_text_object_range)。

TextArea::following_whitespace_end207–216 ↗
fn following_whitespace_end(&self, start: usize) -> usize

作用:从某个位置开始,往后数连续空白一直到哪里结束。它用来决定一个词后面的空格要不要一起选。

数据流:进去的是一个起始位置和当前文本;它从这个位置向后扫描,遇到非空白就停;出来的是连续空白结束的位置。如果一开始就没有空白,就返回原位置。

调用关系:它只被 TextArea::expand_word_around 调用,是 Around 选词时“优先吃掉后面空白”的辅助步骤。

调用图:被 1 处调用(expand_word_around)。

TextArea::preceding_whitespace_start218–227 ↗
fn preceding_whitespace_start(&self, end: usize) -> usize

作用:从某个位置往前看,找到前面连续空白从哪里开始。它用在词后面没有空白时,改为把词前面的空白一起选上。

数据流:进去的是一个结束位置和当前文本;它从这个位置之前向前扫描,连续遇到空白就继续,遇到非空白就停;出来的是这串前置空白的起点。

调用关系:它只被 TextArea::expand_word_around 调用,作为“后面没有空白时”的备用扩展方式。

调用图:被 1 处调用(expand_word_around)。

TextArea::paired_text_object_range229–264 ↗
fn paired_text_object_range(
        &self,
        scope: VimTextObjectScope,
        open: char,
        close: char,
    ) -> Option<Range<usize>>

作用:查找光标所在的成对符号范围,比如圆括号、方括号或花括号。它能区分只选括号里面,还是连括号本身一起选。

数据流:进去的是范围类型,以及开符号和闭符号,比如 '(' 和 ')';它从头扫描文本,用一个栈(一摞临时记录,用来配对嵌套括号)记住开符号,遇到闭符号就配对;它会调用 TextArea::is_inside_element 跳过特殊元素;最后选出包含光标且最小的那一对,出来的是对应范围或 None。

调用关系:它由 TextArea::text_object_range 在对象是括号、方括号或花括号时调用。它是成对符号文本对象的核心查找器。

调用图:调用 1 个内部函数(is_inside_element);被 1 处调用(text_object_range);外部调用 1 个(new)。

TextArea::quoted_text_object_range266–298 ↗
fn quoted_text_object_range(
        &self,
        scope: VimTextObjectScope,
        quote: char,
    ) -> Option<Range<usize>>

作用:查找光标所在的一对引号范围,比如双引号、单引号或反引号。它主要用于“删除引号里的内容”或“复制整段引号字符串”这类操作。

数据流:进去的是范围类型和引号字符;它只扫描当前行,从左到右找成对引号;遇到特殊元素会用 TextArea::is_inside_element 跳过,遇到被反斜杠转义的引号会用 TextArea::is_escaped 跳过;如果要 Around,就用 idx_range 把右引号也算进来;出来的是最小的匹配范围或 None。

调用关系:它由 TextArea::text_object_range 在对象是各种引号时调用。它把引号匹配限制在当前行,避免跨行误选太大一片内容。

调用图:调用 3 个内部函数(is_escaped, is_inside_element, idx_range);被 1 处调用(text_object_range)。

TextArea::is_inside_element300–304 ↗
fn is_inside_element(&self, pos: usize) -> bool

作用:判断某个文字位置是不是落在输入框的特殊元素里面。特殊元素不是普通可解析文本,所以找括号或引号时要绕开它们。

数据流:进去的是一个位置和输入框保存的元素范围列表;它检查这个位置是否在任意元素范围内;出来的是 true 或 false,不修改元素或文本。

调用关系:它被 TextArea::paired_text_object_range 和 TextArea::quoted_text_object_range 调用。它像一道过滤门,防止文本对象查找误读特殊区域里的字符。

调用图:被 2 处调用(paired_text_object_range, quoted_text_object_range)。

TextArea::is_escaped306–315 ↗
fn is_escaped(&self, pos: usize) -> bool

作用:判断某个位置的字符是不是被反斜杠转义了。简单说,如果引号前面有奇数个连续反斜杠,这个引号就只是普通字符,不应该当成字符串边界。

数据流:进去的是一个位置和当前位置之前的文本;它从该位置往前数连续的反斜杠数量;如果数量是奇数就返回 true,否则返回 false。

调用关系:它被 TextArea::quoted_text_object_range 调用。引号查找靠它避开类似 \" 这种不是真正结束引号的情况。

调用图:被 1 处调用(quoted_text_object_range)。

idx_range318–320 ↗
fn idx_range(open_idx: usize, close_idx: usize, quote: char) -> Range<usize>

作用:根据一对引号的位置,生成“连引号本身一起包含”的范围。它是一个很小的辅助函数,让 Around 引号范围的计算更清楚。

数据流:进去的是左引号位置、右引号位置和引号字符;它把范围设为从左引号开始,到右引号字符结束之后;出来的是这个位置范围。

调用关系:它被 TextArea::quoted_text_object_range 在 Around 场景下调用。引号查找函数负责找到两边引号,它负责把边界包装成最终范围。

调用图:被 1 处调用(quoted_text_object_range)。

tui/src/bottom_pane/textarea.rs源码 ↗
domain_logicrequest handling / main loop / rendering

这个文件把一块普通字符串包装成可交互的文本框。它不只是把字塞进去,还要处理很多细节:中文、emoji 这类一个“看起来的字”可能占多个字节,所以光标不能落在半个字符中间;图片占位符等特殊片段要像一个整体,不能被插到一半或删掉一半;窗口宽度变化时,文字要重新折行;删除和复制还要记住“剪切板”。它还支持 Vim 模式,也就是普通模式和插入模式分开,按键先被翻译成移动、删除、复制等动作,再改动文本。可以把它想成输入框里的交通警察:每次按键、粘贴、渲染、移动光标,它都负责让文字、光标、特殊元素和屏幕显示保持一致。 从这段代码可以看出,这个文件不只是把文字画到屏幕上。它还要保证文字显示、光标位置和内部数据一直对得上。比如 emoji 或中文在屏幕上可能占两个格子,组合字符看起来是一个字但内部可能有多个编码点,所以光标不能随便停在“半个字”中间。render_lines 会把普通文字先画出来,再叠加特殊元素样式和临时高亮;render_lines_masked 则像密码框一样,把真实内容换成掩码字符显示。后面的大量测试像安全网,检查插入、删除、按词删除、Vim 模式、复制粘贴、自动换行、滚动和各种键盘组合都不会出错。 从这段代码能看出,这个文件里的文本框不是简单的一行字符串。它要处理多字节字符、光标移动、按词跳转、删除一段文字、自动换行、滚动显示,还要支持“元素”:一段被当成整体的小块文本,像贴在输入框里的标签,光标不能停在它中间,替换时也要把它当成一个整体。这里给出的函数是一个随机压力测试。它每天用一个固定但会变化的随机种子,反复制造很多乱序操作:插入字符、删除、替换、移动光标、改变显示宽高、插入元素、渲染到终端缓冲区。每一步后都检查几条底线:光标不能跑到文本外面,不能钻进元素内部,渲染和计算光标位置不能崩溃,滚动状态也要合理。它像是在让输入框经历一场“熊孩子乱按键盘”的考试,确保真实使用时不容易被奇怪输入弄坏。

函数细节193
is_word_separator48–50 ↗
fn is_word_separator(ch: char) -> bool

作用:判断一个字符是不是“单词分隔符”,比如标点这类会把单词切开的字符。它用于按单词移动或删除时,决定哪里算一个词的边界。

数据流:输入一个字符 → 去预先定义的分隔符列表里查它在不在 → 输出 true 或 false,不改任何状态。

调用关系:它是 split_word_pieces 的小工具;当文本框要找上一个词或下一个词时,会间接用到它来切词。

调用图:被 1 处调用(split_word_pieces)。

split_word_pieces52–76 ↗
fn split_word_pieces(run: &str) -> Vec<(usize, &str)>

作用:把一段连续非空白文字切成更小的片段,把普通字母数字和分隔符分开。这样按“跳到下一个词”时,不会把标点和词混成一团。

数据流:输入一段字符串 → 先按 Unicode 单词边界粗分,再按分隔符和非分隔符变化细分 → 输出每片在原字符串里的起点和内容。

调用关系:beginning_of_previous_word 和 end_of_next_word_from 会用它理解“词”的范围;它内部调用 is_word_separator 判断字符类型。

调用图:调用 1 个内部函数(is_word_separator);被 2 处调用(beginning_of_previous_word, end_of_next_word_from);外部调用 1 个(new)。

TextArea::new139–158 ↗
fn new() -> Self

作用:创建一个空的文本框,并把光标、剪切板、Vim 状态和默认按键配置都设好。新输入框刚出现时就靠它初始化。

数据流:不需要输入 → 读取默认按键表,创建空文本、光标位置 0、空元素列表、默认插入模式等状态 → 返回一个可用的 TextArea。

调用关系:外层界面或测试创建输入框时会调用它;后续所有输入、渲染和编辑操作都建立在这些初始状态上。

调用图:调用 1 个内部函数(defaults);被 20 处调用(new, test_current_at_token_allows_file_queries_with_second_at, test_current_at_token_basic_cases, test_current_at_token_cursor_positions, test_current_at_token_ignores_mid_word_at, test_current_at_token_tracks_tokens_with_second_at, test_current_at_token_whitespace_boundaries, new, new, delete_forward_deletes_element_at_left_edge (+10 more));外部调用 3 个(new, new, new)。

TextArea::set_keymap_bindings166–171 ↗
fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap)

作用:替换文本框使用的按键绑定,也就是告诉它哪些键代表删除、移动、Vim 操作等。用户自定义快捷键时会用到。

数据流:输入一份 RuntimeKeymap → 拷贝其中普通编辑和 Vim 各模式的按键表 → 更新文本框内部的按键配置。

调用关系:它通常在配置加载后被调用;之后 input、handle_vim_normal、handle_vim_operator 等处理按键时都会按新配置工作。

TextArea::set_text_clearing_elements179–181 ↗
fn set_text_clearing_elements(&mut self, text: &str)

作用:把文本框内容整体换成新文字,并清掉原来的特殊元素标记。适合把输入框重置成纯文本。

数据流:输入新字符串 → 交给 set_text_inner,明确不带元素列表 → 文本被替换,元素被清空,光标被调整到安全位置。

调用关系:它是 set_text_inner 的公开入口之一;调用者不关心图片占位符等元素时会用这个版本。

调用图:调用 1 个内部函数(set_text_inner)。

TextArea::set_text_with_elements188–190 ↗
fn set_text_with_elements(&mut self, text: &str, elements: &[UserTextElement])

作用:把文本框内容整体换成新文字,同时重新设置哪些范围是特殊元素。适合恢复带图片占位符等结构化片段的输入。

数据流:输入新字符串和元素范围列表 → 交给 set_text_inner → 文本、元素范围、光标和换行缓存一起更新。

调用关系:它是 set_text_inner 的另一个公开入口;需要保留特殊元素信息的上层功能会用它。

调用图:调用 1 个内部函数(set_text_inner)。

TextArea::set_text_inner192–221 ↗
fn set_text_inner(&mut self, text: &str, elements: Option<&[UserTextElement]>)

作用:真正执行“整段替换文本”的内部函数,负责保证新文本、特殊元素和光标都合法。没有它,光标可能落到半个中文字符里,元素范围也可能越界。

数据流:输入新文本和可选元素列表 → 替换原文本,重建元素范围,给元素分配新 id,修正光标,清掉换行缓存和列偏好 → 内部状态变成和新文本一致。

调用关系:set_text_clearing_elements 和 set_text_with_elements 都把活交给它;它会调用字符边界修正和 next_element_id 等底层工具。

调用图:调用 3 个内部函数(clamp_pos_to_char_boundary, clamp_pos_to_nearest_boundary, next_element_id);被 2 处调用(set_text_clearing_elements, set_text_with_elements)。

TextArea::set_vim_enabled229–237 ↗
fn set_vim_enabled(&mut self, enabled: bool)

作用:打开或关闭 Vim 编辑方式。打开时进入普通模式,关闭时回到普通输入的插入状态。

数据流:输入是否启用 → 更新 vim_enabled,清掉未完成的 Vim 操作,并设置当前 Vim 模式 → 文本内容不变。

调用关系:外层设置切换 Vim 功能时调用;之后 input 会根据这个开关选择普通编辑还是 Vim 编辑。

TextArea::is_vim_enabled240–242 ↗
fn is_vim_enabled(&self) -> bool

作用:告诉调用者 Vim 模式总开关现在是不是打开。界面显示或逻辑判断会用它。

数据流:读取内部 vim_enabled → 返回布尔值 → 不修改任何内容。

调用关系:它是状态查询函数;外层组件可用它决定是否显示 Vim 相关提示。

TextArea::is_vim_normal_mode249–251 ↗
fn is_vim_normal_mode(&self) -> bool

作用:判断现在是否处在 Vim 的普通模式,也就是按键主要用于移动、删除、复制,而不是直接输入文字。

数据流:读取 vim_enabled 和 vim_mode → 两者满足条件就返回 true → 不改状态。

调用关系:外层界面可用它决定光标样式或按键解释;内部状态由 set_vim_enabled、enter_vim_normal_mode 等函数改变。

TextArea::vim_normal_end_cursor254–260 ↗
fn vim_normal_end_cursor(&self) -> usize

作用:给 Vim 普通模式计算“文本末尾光标应该停在哪里”。Vim 普通模式通常停在最后一个可见字符上,而不是停在字符后面。

数据流:读取文本长度 → 空文本返回 0,否则找到最后一个完整字符或元素的前一个边界 → 返回安全光标位置。

调用关系:它依赖 prev_atomic_boundary;外层需要按 Vim 规则放置光标时会用它。

调用图:调用 1 个内部函数(prev_atomic_boundary)。

TextArea::is_vim_operator_pending266–268 ↗
fn is_vim_operator_pending(&self) -> bool

作用:判断 Vim 是否正在等第二个按键,比如已经按了 d,正在等后面的 w 或 d。这样界面可以知道有一个未完成命令。

数据流:读取 vim_pending → 如果不是 None 就返回 true → 不改状态。

调用关系:它是 Vim 状态查询;handle_vim_normal 会设置或清掉 vim_pending。

调用图:外部调用 1 个(matches!)。

TextArea::enter_vim_insert_mode275–280 ↗
fn enter_vim_insert_mode(&mut self)

作用:在 Vim 开启时切到插入模式,让后续普通字符能直接写入文本。

数据流:读取 vim_enabled → 如果开启,就把 vim_mode 设为 Insert 并清掉待处理命令 → 文本不变。

调用关系:它配合 enter_vim_normal_mode 使用;Vim 命令或外层控制可调用它改变输入方式。

TextArea::enter_vim_normal_mode288–294 ↗
fn enter_vim_normal_mode(&mut self)

作用:在 Vim 开启时切回普通模式,让后续按键变成移动、删除、复制等命令。

数据流:读取 vim_enabled → 如果开启,设置 Normal,清掉待处理命令和垂直移动记忆列 → 文本不变。

调用关系:handle_vim_insert 在收到 Esc 时会调用它;这是从打字状态回到命令状态的关键切换点。

调用图:被 1 处调用(handle_vim_insert)。

TextArea::allows_paste_burst301–303 ↗
fn allows_paste_burst(&self) -> bool

作用:判断现在是否允许一次性粘贴一大段文字。Vim 普通模式下粘贴原始字符可能会被误当命令,所以要拦住。

数据流:读取 Vim 开关和当前模式 → 非 Vim 或 Vim 插入模式返回 true,否则 false → 不改状态。

调用关系:handle_key_event_at 会用它决定粘贴事件该不该直接送进文本框。

调用图:被 1 处调用(handle_key_event_at)。

TextArea::uses_vim_insert_cursor306–308 ↗
fn uses_vim_insert_cursor(&self) -> bool

作用:判断当前是否应该使用 Vim 插入模式的光标样式。界面可以据此改变光标显示。

数据流:读取 vim_enabled 和 vim_mode → 如果 Vim 开启且处于 Insert 就返回 true → 不改状态。

调用关系:它是给渲染或外层状态栏使用的查询函数;模式变化由 Vim 输入处理函数维护。

TextArea::should_handle_vim_insert_escape315–321 ↗
fn should_handle_vim_insert_escape(&self, event: KeyEvent) -> bool

作用:判断某个按键事件是不是 Vim 插入模式里的 Esc。它用来把 Esc 解释成“退出插入模式”,而不是普通字符。

数据流:输入一个 KeyEvent → 检查 Vim 是否开启、是否插入模式、是否无修饰 Esc、是否按下或重复 → 返回是否应处理。

调用关系:外层按键处理可先问它;真正切换模式由 handle_vim_insert 或 enter_vim_normal_mode 完成。

调用图:外部调用 1 个(matches!)。

TextArea::vim_mode_label328–336 ↗
fn vim_mode_label(&self) -> Option<&'static str>

作用:返回当前 Vim 模式的显示文字,比如 Normal 或 Insert。状态栏显示模式时会用到。

数据流:读取 Vim 开关和模式 → 关闭时返回 None,开启时返回固定英文标签 → 不改状态。

调用关系:它服务于界面展示;模式由 set_vim_enabled 和 Vim 输入处理改变。

TextArea::text338–340 ↗
fn text(&self) -> &str

作用:把当前输入框里的完整文本借出去看。提交、渲染或查找当前 token 时会用它。

数据流:读取内部 text 字符串 → 返回只读引用 → 不修改文本框。

调用关系:current_prefixed_token_range、handle_key_event_at、render、submit 等上层流程会调用它拿到当前内容。

调用图:被 5 处调用(current_prefixed_token_range, handle_key_event_at, render, render, submit)。

TextArea::insert_str342–344 ↗
fn insert_str(&mut self, text: &str)

作用:在当前光标位置插入一段文字。普通打字和粘贴都会走这条路。

数据流:输入要插入的字符串 → 使用当前 cursor_pos 作为位置,交给 insert_str_at → 文本变长,光标和元素位置随之更新。

调用关系:input_with_keymap、handle_paste、yank、paste_after_cursor 等会调用它;具体安全插入由 insert_str_at 完成。

调用图:调用 1 个内部函数(insert_str_at);被 6 处调用(handle_key_event_at, handle_paste, handle_paste, input_with_keymap, paste_after_cursor, yank)。

TextArea::insert_str_at346–355 ↗
fn insert_str_at(&mut self, pos: usize, text: &str)

作用:在指定位置插入文字,并保证不会插进半个字符或特殊元素中间。它是所有插入操作的底层入口。

数据流:输入位置和文字 → 先把位置修正到可插入边界,插入字符串,清掉换行缓存,必要时移动光标,平移后面的元素范围 → 文本和结构同步更新。

调用关系:insert_str、insert_element、Vim 打开新行和行粘贴都会用它;它调用 clamp_pos_for_insertion 和 shift_elements 做安全处理。

调用图:调用 2 个内部函数(clamp_pos_for_insertion, shift_elements);被 4 处调用(handle_vim_normal, insert_element, insert_str, paste_line_after_current_line)。

TextArea::replace_range357–360 ↗
fn replace_range(&mut self, range: std::ops::Range<usize>, text: &str)

作用:把一段文本替换成另一段,并且尊重特殊元素边界。外部删除单字时也会用它。

数据流:输入要替换的字节范围和新文字 → 先把范围扩展到完整覆盖相交元素 → 交给 replace_range_raw 真正替换。

调用关系:delete_backward 和 delete_forward 调用它;它防止调用者只删掉图片占位符的一半。

调用图:调用 2 个内部函数(expand_range_to_element_boundaries, replace_range_raw);被 2 处调用(delete_backward, delete_forward)。

TextArea::replace_range_raw362–393 ↗
fn replace_range_raw(&mut self, range: std::ops::Range<usize>, text: &str)

作用:真正执行范围替换,并同步更新光标、换行缓存和元素范围。它假设调用者已经决定好范围。

数据流:输入范围和新文字 → 裁剪范围到文本长度,替换字符串,更新元素,按替换前后长度差调整光标,再把光标挪出元素内部 → 内部文本状态改变。

调用关系:replace_range 和 kill_range_with_kind 调用它;它把元素更新交给 update_elements_after_replace。

调用图:调用 2 个内部函数(clamp_pos_to_nearest_boundary, update_elements_after_replace);被 2 处调用(kill_range_with_kind, replace_range);外部调用 1 个(assert!)。

TextArea::cursor395–397 ↗
fn cursor(&self) -> usize

作用:返回当前光标在字符串里的字节位置。需要知道插入点在哪里的功能会用它。

数据流:读取 cursor_pos → 返回这个数字 → 不改状态。

调用关系:current_prefixed_token_range 和 handle_remote_image_selection_key 等外层逻辑用它定位当前上下文。

调用图:被 2 处调用(current_prefixed_token_range, handle_remote_image_selection_key)。

TextArea::set_cursor399–403 ↗
fn set_cursor(&mut self, pos: usize)

作用:把光标放到指定位置,同时修正到安全边界。这样调用者不用担心把光标放进半个字符或元素内部。

数据流:输入目标位置 → 限制在文本长度内,再贴到最近合法边界,清掉垂直移动记忆列 → 更新 cursor_pos。

调用关系:Vim、普通移动、插入元素、粘贴等许多流程都会调用它;它依赖 clamp_pos_to_nearest_boundary。

调用图:调用 1 个内部函数(clamp_pos_to_nearest_boundary);被 8 处调用(handle_vim_normal, input_with_keymap, insert_element, move_cursor_to_beginning_of_line, move_cursor_to_end_of_line, paste_after_cursor, paste_line_after_current_line, target_for_motion)。

TextArea::desired_height405–407 ↗
fn desired_height(&self, width: u16) -> u16

作用:计算在给定宽度下,这个文本框显示完整内容需要多少行高。布局系统可以用它决定输入框高度。

数据流:输入宽度 → 取得按该宽度折行后的行列表 → 返回行数。

调用关系:input_height 会调用它;折行计算由 wrapped_lines 负责,并带缓存。

调用图:调用 1 个内部函数(wrapped_lines);被 2 处调用(input_height, input_height)。

TextArea::cursor_pos410–412 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:计算光标在屏幕区域里的坐标。终端界面需要它把真实光标画到正确位置。

数据流:输入屏幕矩形 area → 使用默认滚动状态调用 cursor_pos_with_state → 返回可见坐标或 None。

调用关系:它是 cursor_pos_with_state 的简化入口;渲染或光标定位代码会用它。

调用图:调用 1 个内部函数(cursor_pos_with_state);外部调用 1 个(default)。

TextArea::cursor_pos_with_state415–426 ↗
fn cursor_pos_with_state(&self, area: Rect, state: TextAreaState) -> Option<(u16, u16)>

作用:在考虑换行和滚动后,算出光标应该显示在屏幕上的哪一列哪一行。

数据流:输入显示区域和滚动状态 → 取折行结果,修正滚动,让光标所在行可见,再计算光标前文字宽度 → 返回屏幕坐标。

调用关系:cursor_pos 调用它;它和 render_ref 使用同一套 wrapped_lines、effective_scroll 逻辑,保证画出来和光标位置一致。

调用图:调用 2 个内部函数(effective_scroll, wrapped_lines);被 3 处调用(cursor_pos, cursor_pos, cursor_pos);外部调用 1 个(wrapped_line_index_by_start)。

TextArea::is_empty428–430 ↗
fn is_empty(&self) -> bool

作用:判断文本框当前是不是空的。提交按钮、占位提示等逻辑可能会用到。

数据流:读取 text → 返回是否为空字符串 → 不改状态。

调用关系:这是简单查询函数,供外层逻辑判断当前输入状态。

TextArea::current_display_col432–435 ↗
fn current_display_col(&self) -> usize

作用:计算光标在当前逻辑行里的显示列数。这里的列不是字节数,而是屏幕上占几格。

数据流:读取当前行开头到光标之间的文字 → 按字符显示宽度相加 → 返回列号。

调用关系:move_cursor_up 和 move_cursor_down 用它记住垂直移动时想保持的列。

调用图:调用 1 个内部函数(beginning_of_current_line);被 2 处调用(move_cursor_down, move_cursor_up)。

TextArea::wrapped_line_index_by_start437–442 ↗
fn wrapped_line_index_by_start(lines: &[Range<usize>], pos: usize) -> Option<usize>

作用:根据一个文本位置,找它属于哪一条折行后的可视行。折行后,一行长文字可能变成多条屏幕行。

数据流:输入折行范围列表和位置 → 用二分式查找找到最后一个起点不大于该位置的范围 → 返回行索引或 None。

调用关系:cursor_pos_with_state、上下移动、effective_scroll 都用它把字节位置映射到屏幕行。

调用图:外部调用 1 个(partition_point)。

TextArea::move_to_display_col_on_line444–462 ↗
fn move_to_display_col_on_line(
        &mut self,
        line_start: usize,
        line_end: usize,
        target_col: usize,
    )

作用:把光标移动到某一行里尽量接近目标显示列的位置。上下移动时用它保持“同一列”的感觉。

数据流:输入行起止范围和目标列 → 按字形逐个累计显示宽度,找到超过目标列的位置 → 设置光标,并修正到安全边界。

调用关系:move_cursor_up 和 move_cursor_down 调用它;它会调用 clamp_pos_to_nearest_boundary 避免落入元素中间。

调用图:调用 1 个内部函数(clamp_pos_to_nearest_boundary);被 2 处调用(move_cursor_down, move_cursor_up)。

TextArea::beginning_of_line464–466 ↗
fn beginning_of_line(&self, pos: usize) -> usize

作用:找某个位置所在行的开头。行开头就是前一个换行符之后的位置。

数据流:输入字节位置 → 在它前面的文本里倒着找换行符 → 返回行开头位置,找不到就返回 0。

调用关系:beginning_of_current_line、行级 Vim 操作和移动到行首都会用它。

调用图:被 3 处调用(beginning_of_current_line, linewise_range_for_vertical_motion, move_cursor_to_beginning_of_line)。

TextArea::beginning_of_current_line467–469 ↗
fn beginning_of_current_line(&self) -> usize

作用:找当前光标所在行的开头。很多按行操作都要先知道这一点。

数据流:读取 cursor_pos → 调用 beginning_of_line → 返回当前行起点。

调用关系:当前列计算、删到行首、Vim 行首移动、当前行范围计算等都会调用它。

调用图:调用 1 个内部函数(beginning_of_line);被 9 处调用(current_display_col, current_line_range_with_newline, first_non_blank_of_current_line, handle_vim_insert, handle_vim_normal, kill_to_beginning_of_line, move_cursor_to_beginning_of_line, target_for_motion, vim_line_end_cursor)。

TextArea::first_non_blank_of_current_line471–478 ↗
fn first_non_blank_of_current_line(&self) -> usize

作用:找到当前行第一个不是空白的字符位置。Vim 的 I 命令通常会跳到这里开始插入。

数据流:取得当前行起点和终点 → 从行内往后找第一个非空白字符 → 找到就返回位置,否则返回行尾。

调用关系:handle_vim_normal 在处理插入到行首非空白处时调用它。

调用图:调用 2 个内部函数(beginning_of_current_line, end_of_current_line);被 1 处调用(handle_vim_normal)。

TextArea::end_of_line480–485 ↗
fn end_of_line(&self, pos: usize) -> usize

作用:找某个位置所在行的行尾,也就是下一个换行符之前的位置。

数据流:输入位置 → 从该位置往后找换行符 → 返回换行符位置,找不到就返回文本末尾。

调用关系:end_of_current_line、行级范围、移动到行尾等函数会用它。

调用图:被 3 处调用(end_of_current_line, linewise_range_for_vertical_motion, move_cursor_to_end_of_line)。

TextArea::end_of_current_line486–488 ↗
fn end_of_current_line(&self) -> usize

作用:找当前光标所在行的行尾。删除到行尾、粘贴整行、Vim 行尾移动都靠它定位。

数据流:读取 cursor_pos → 调用 end_of_line → 返回当前行结束位置。

调用关系:kill_to_end_of_line、paste_line_after_current_line、handle_vim_normal 等都会调用它。

调用图:调用 1 个内部函数(end_of_line);被 9 处调用(current_line_range_with_newline, first_non_blank_of_current_line, handle_vim_normal, kill_to_end_of_line, move_cursor_to_end_of_line, paste_line_after_current_line, target_for_motion, vim_kill_to_end_of_line, vim_line_end_cursor)。

TextArea::input490–502 ↗
fn input(&mut self, event: KeyEvent)

作用:接收一个键盘事件,并把它送到正确的编辑模式里处理。它是文本框按键处理的总入口。

数据流:输入 KeyEvent → 忽略松键事件,只处理按下和重复;如果 Vim 开启就交给 Vim 流程,否则按普通键位表处理 → 文本或光标可能改变。

调用关系:handle_key_event_at 和 handle_key_event 会调用它;它再分发给 handle_vim_input 或 input_with_keymap。

调用图:调用 2 个内部函数(handle_vim_input, input_with_keymap);被 2 处调用(handle_key_event_at, handle_key_event);外部调用 2 个(matches!, clone)。

TextArea::input_with_keymap504–620 ↗
fn input_with_keymap(&mut self, event: KeyEvent, keymap: &EditorKeymap)

作用:按普通编辑器的快捷键表解释一个按键,比如回车、退格、移动、剪切、粘贴和输入字符。

数据流:输入 KeyEvent 和编辑键位表 → 按优先级匹配各种快捷键;匹配后执行插入、删除、移动或 yank;普通字符会插入文本 → 状态按具体动作变化。

调用关系:普通 input 会调用它,Vim 插入模式也会调用它;它把具体工作交给 delete_backward、insert_str、move_cursor_left 等编辑函数。

调用图:调用 19 个内部函数(beginning_of_previous_word, delete_backward, delete_backward_word, delete_forward, delete_forward_word, end_of_next_word, insert_str, kill_current_line, kill_to_beginning_of_line, kill_to_end_of_line (+9 more));被 2 处调用(handle_vim_insert, input);外部调用 2 个(matches!, debug!)。

TextArea::handle_vim_input622–627 ↗
fn handle_vim_input(&mut self, event: KeyEvent)

作用:把 Vim 模式下的按键分给插入模式或普通模式处理。它是 Vim 按键处理的小分发器。

数据流:输入 KeyEvent → 查看当前 vim_mode → Insert 交给 handle_vim_insert,Normal 交给 handle_vim_normal。

调用关系:input 在 Vim 开启时调用它;真正的编辑动作由两个模式处理函数继续完成。

调用图:调用 2 个内部函数(handle_vim_insert, handle_vim_normal);被 1 处调用(input)。

TextArea::handle_vim_insert629–640 ↗
fn handle_vim_insert(&mut self, event: KeyEvent)

作用:处理 Vim 插入模式里的按键。大多数键像普通编辑器一样输入,Esc 会回到普通模式。

数据流:输入 KeyEvent → 如果是 Esc,光标按 Vim 习惯退到前一个字符并切普通模式;否则用普通键位表处理 → 文本或模式可能改变。

调用关系:handle_vim_input 调用它;它会调用 enter_vim_normal_mode 或 input_with_keymap。

调用图:调用 4 个内部函数(beginning_of_current_line, enter_vim_normal_mode, input_with_keymap, prev_atomic_boundary);被 1 处调用(handle_vim_input);外部调用 2 个(matches!, clone)。

TextArea::handle_vim_normal642–784 ↗
fn handle_vim_normal(&mut self, event: KeyEvent)

作用:处理 Vim 普通模式里的命令键,比如 h/j/k/l 移动、w 跳词、x 删除、p 粘贴、d/y/c 等操作符。它让输入框像 Vim 那样先发命令再改文本。

数据流:输入 KeyEvent → 先处理上一次遗留的操作符或文本对象;否则按 Vim 普通模式键位逐项匹配 → 更新光标、切模式、删除、复制、粘贴或记录待完成命令。

调用关系:handle_vim_input 在普通模式下调用它;它会把复杂命令继续交给 handle_vim_operator、handle_vim_text_object 和各类移动/删除函数。

调用图:调用 20 个内部函数(beginning_of_current_line, beginning_of_next_word, beginning_of_previous_word, delete_forward_kill, end_of_current_line, first_non_blank_of_current_line, handle_vim_operator, handle_vim_text_object, insert_str_at, move_cursor_down (+10 more));被 1 处调用(handle_vim_input);外部调用 2 个(replace, Operator)。

TextArea::handle_vim_operator786–813 ↗
fn handle_vim_operator(&mut self, op: VimOperator, event: KeyEvent) -> bool

作用:处理 Vim 里已经按下 d、y 这类操作符之后的第二个键。比如 dd 删除整行,yw 复制到下一个词。

数据流:输入操作符和按键 → 识别整行操作、取消、文本对象开头或移动命令 → 执行对应操作,或记录还在等待文本对象 → 返回是否处理成功。

调用关系:handle_vim_normal 在发现有待处理操作符时调用它;它会调用 kill_current_line、yank_current_line、vim_motion_for_event、apply_vim_operator。

调用图:调用 4 个内部函数(apply_vim_operator, kill_current_line, vim_motion_for_event, yank_current_line);被 1 处调用(handle_vim_normal)。

TextArea::handle_vim_text_object815–831 ↗
fn handle_vim_text_object(
        &mut self,
        op: VimOperator,
        scope: VimTextObjectScope,
        event: KeyEvent,
    ) -> bool

作用:处理 Vim 文本对象命令,比如“删除括号内内容”这类先选范围再操作的命令。

数据流:输入操作符、范围类型和按键 → 如果取消就结束;否则把按键识别成文本对象,找到对应文本范围,再应用删除、复制或修改 → 文本或剪切板可能改变。

调用关系:handle_vim_normal 在等待文本对象时调用它;它把最终执行交给 apply_vim_operator_to_range。

调用图:调用 1 个内部函数(apply_vim_operator_to_range);被 1 处调用(handle_vim_normal)。

TextArea::vim_motion_for_event833–870 ↗
fn vim_motion_for_event(&self, event: KeyEvent) -> Option<VimMotion>

作用:把一个 Vim 按键翻译成抽象移动动作,比如左、右、上、下、到词尾、到行尾。

数据流:输入 KeyEvent → 按 Vim 操作符键位表逐项匹配 → 返回 VimMotion 或 None。

调用关系:handle_vim_operator 用它理解 d 后面的 w、$ 等键;真正算范围由 range_for_motion 完成。

调用图:被 1 处调用(handle_vim_operator)。

TextArea::apply_vim_operator872–881 ↗
fn apply_vim_operator(&mut self, op: VimOperator, motion: VimMotion)

作用:把 Vim 操作符应用到某个移动动作形成的范围上。比如 d+w 会先算范围再删除。

数据流:输入操作符和移动动作 → 计算该动作覆盖的文本范围;Delete 删除并存入剪切板,Yank 只复制,Change 在这里不处理 → 文本或剪切板可能改变。

调用关系:handle_vim_operator 调用它;它依赖 range_for_motion、kill_range 和 yank_range。

调用图:调用 3 个内部函数(kill_range, range_for_motion, yank_range);被 1 处调用(handle_vim_operator)。

TextArea::apply_vim_operator_to_range883–892 ↗
fn apply_vim_operator_to_range(&mut self, op: VimOperator, range: Range<usize>)

作用:把 Vim 操作符直接应用到已经算好的文本范围上。文本对象命令会用这种方式。

数据流:输入操作符和范围 → Delete 删除,Yank 复制,Change 删除后切到插入模式 → 文本、剪切板或模式可能改变。

调用关系:handle_vim_text_object 调用它;它最终使用 kill_range 或 yank_range 完成实际改动。

调用图:调用 2 个内部函数(kill_range, yank_range);被 1 处调用(handle_vim_text_object)。

TextArea::range_for_motion894–909 ↗
fn range_for_motion(&mut self, motion: VimMotion) -> Option<Range<usize>>

作用:根据 Vim 的一个移动动作,算出从当前光标到目标位置之间的范围。删除或复制移动范围时要先用它。

数据流:输入 VimMotion → 上下移动按整行范围处理;其他移动先算目标位置,再把起点终点排好 → 返回范围或 None。

调用关系:apply_vim_operator 调用它;它把上下移动交给 linewise_range_for_vertical_motion,其他交给 target_for_motion。

调用图:调用 2 个内部函数(linewise_range_for_vertical_motion, target_for_motion);被 1 处调用(apply_vim_operator);外部调用 1 个(matches!)。

TextArea::linewise_range_for_vertical_motion911–944 ↗
fn linewise_range_for_vertical_motion(&self, motion: VimMotion) -> Option<Range<usize>>

作用:给 Vim 的上移/下移操作算整行范围。因为 d+j 这类命令按 Vim 习惯是删整行,不是删一个矩形区域。

数据流:输入移动方向 → 取当前行范围;向上扩展到上一行开头,向下扩展到下一行结尾 → 返回非空范围。

调用关系:range_for_motion 在处理 Up 或 Down 时调用它;它依赖 beginning_of_line、end_of_line 和 current_line_range_with_newline。

调用图:调用 3 个内部函数(beginning_of_line, current_line_range_with_newline, end_of_line);被 1 处调用(range_for_motion)。

TextArea::target_for_motion946–964 ↗
fn target_for_motion(&mut self, motion: VimMotion) -> usize

作用:临时执行一次移动,只为了知道目标位置在哪里,然后把光标恢复原样。这样算删除范围时不会提前真的移动光标。

数据流:输入移动动作 → 保存当前光标和记忆列,执行对应移动,记下目标位置,再恢复原状态 → 返回目标位置。

调用关系:range_for_motion 调用它;它复用现有 move_cursor_* 和词/行定位函数,避免重复写移动规则。

调用图:调用 10 个内部函数(beginning_of_current_line, beginning_of_next_word, beginning_of_previous_word, end_of_current_line, move_cursor_down, move_cursor_left, move_cursor_right, move_cursor_up, set_cursor, vim_word_end_exclusive);被 1 处调用(range_for_motion)。

TextArea::delete_backward967–979 ↗
fn delete_backward(&mut self, n: usize)

作用:从光标前面删除 n 个“原子单位”。原子单位可以是一个完整字形,也可以是一个特殊元素整体。

数据流:输入数量 n → 从当前光标往前跳 n 次安全边界 → 用 replace_range 删除这段内容 → 文本变短,光标和元素更新。

调用关系:input_with_keymap 在退格时调用它;它依赖 prev_atomic_boundary 和 replace_range。

调用图:调用 2 个内部函数(prev_atomic_boundary, replace_range);被 1 处调用(input_with_keymap)。

TextArea::delete_forward981–993 ↗
fn delete_forward(&mut self, n: usize)

作用:从光标后面删除 n 个完整单位,类似 Delete 键。不会删半个 emoji 或半个特殊元素。

数据流:输入数量 n → 从光标往后找 n 次安全边界 → 用 replace_range 删除光标到目标之间内容 → 文本和元素更新。

调用关系:input_with_keymap 在前删键时调用它;它依赖 next_atomic_boundary 和 replace_range。

调用图:调用 2 个内部函数(next_atomic_boundary, replace_range);被 1 处调用(input_with_keymap)。

TextArea::delete_forward_kill995–1007 ↗
fn delete_forward_kill(&mut self, n: usize)

作用:向前删除 n 个单位,并把删掉的内容放进剪切板。Vim 的 x 这类命令会用它。

数据流:输入数量 n → 找到向后的安全目标位置 → kill_range 删除并保存这段文字 → 文本变短,剪切板更新。

调用关系:handle_vim_normal 调用它;它和普通 delete_forward 的区别是会记录 kill buffer。

调用图:调用 2 个内部函数(kill_range, next_atomic_boundary);被 1 处调用(handle_vim_normal)。

TextArea::delete_backward_word1009–1012 ↗
fn delete_backward_word(&mut self)

作用:删除光标前的一个词,并把删除内容记入剪切板。常见于 Ctrl+Backspace 这类快捷键。

数据流:读取当前光标 → 找上一个词的开头 → kill_range 删除从那里到光标的内容 → 文本和剪切板更新。

调用关系:input_with_keymap 调用它;词边界由 beginning_of_previous_word 计算。

调用图:调用 2 个内部函数(beginning_of_previous_word, kill_range);被 1 处调用(input_with_keymap)。

TextArea::delete_forward_word1019–1024 ↗
fn delete_forward_word(&mut self)

作用:删除光标后的一个词,并把删除内容记入剪切板。常见于向前删词快捷键。

数据流:读取当前光标 → 找下一个词的结束位置 → 如果确实在光标后,就 kill_range 删除 → 文本和剪切板更新。

调用关系:input_with_keymap 调用它;词尾由 end_of_next_word 计算。

调用图:调用 2 个内部函数(end_of_next_word, kill_range);被 1 处调用(input_with_keymap)。

TextArea::kill_to_end_of_line1032–1047 ↗
fn kill_to_end_of_line(&mut self)

作用:删除从光标到行尾的内容,并保存到剪切板;如果已经在行尾,就删除换行符。类似命令行里的 Ctrl+K。

数据流:读取当前光标和行尾 → 选择光标到行尾或行尾换行符范围 → kill_range 删除并保存 → 文本变短。

调用关系:input_with_keymap 调用它;Vim 版本有单独的 vim_kill_to_end_of_line。

调用图:调用 2 个内部函数(end_of_current_line, kill_range);被 1 处调用(input_with_keymap)。

TextArea::vim_kill_to_end_of_line1049–1054 ↗
fn vim_kill_to_end_of_line(&mut self)

作用:按 Vim 规则删除从光标到当前行尾的内容。它不会在光标正好在行尾时额外删掉换行符。

数据流:读取行尾 → 如果光标在行尾前,就 kill_range 删除到行尾 → 文本和剪切板更新,否则不动。

调用关系:handle_vim_normal 处理 D 或 C 这类到行尾命令时调用它。

调用图:调用 2 个内部函数(end_of_current_line, kill_range);被 1 处调用(handle_vim_normal)。

TextArea::kill_to_beginning_of_line1056–1067 ↗
fn kill_to_beginning_of_line(&mut self)

作用:删除从行首到光标的内容,并保存到剪切板;如果已经在行首,就删除前面的换行符。类似命令行里的 Ctrl+U。

数据流:读取当前行首和光标 → 选择行首到光标或前一个换行符范围 → kill_range 删除并保存 → 文本变短。

调用关系:input_with_keymap 调用它;它依赖 beginning_of_current_line。

调用图:调用 2 个内部函数(beginning_of_current_line, kill_range);被 1 处调用(input_with_keymap)。

TextArea::yank1074–1080 ↗
fn yank(&mut self)

作用:把剪切板里的内容插回当前光标位置。这里的 yank 是 Emacs/Vim 里“粘贴刚删或刚复制内容”的意思。

数据流:读取 kill_buffer → 如果为空就不动;否则复制一份内容并 insert_str 插入 → 文本变长。

调用关系:input_with_keymap 在 yank 快捷键触发时调用它;插入细节交给 insert_str。

调用图:调用 1 个内部函数(insert_str);被 1 处调用(input_with_keymap)。

TextArea::kill_range1082–1084 ↗
fn kill_range(&mut self, range: Range<usize>)

作用:删除一段字符级范围,并把内容存入剪切板。字符级表示粘贴时按普通文本插入。

数据流:输入范围 → 交给 kill_range_with_kind,标记为 Characterwise → 文本和剪切板可能改变。

调用关系:多个删除词、删除到行首/行尾、Vim 删除操作都会调用它。

调用图:调用 1 个内部函数(kill_range_with_kind);被 8 处调用(apply_vim_operator, apply_vim_operator_to_range, delete_backward_word, delete_forward_kill, delete_forward_word, kill_to_beginning_of_line, kill_to_end_of_line, vim_kill_to_end_of_line)。

TextArea::kill_line_range1086–1088 ↗
fn kill_line_range(&mut self, range: Range<usize>)

作用:删除一整行范围,并把内容按“整行”类型存入剪切板。这样 Vim 的 p 粘贴时会贴到下一行。

数据流:输入行范围 → 交给 kill_range_with_kind,标记为 Linewise → 文本和剪切板更新。

调用关系:kill_current_line 调用它;paste_after_cursor 会根据这个类型选择行粘贴。

调用图:调用 1 个内部函数(kill_range_with_kind);被 1 处调用(kill_current_line)。

TextArea::kill_range_with_kind1090–1103 ↗
fn kill_range_with_kind(&mut self, range: Range<usize>, kind: KillBufferKind)

作用:删除指定范围、保存删除内容,并记录这次剪切是字符级还是整行级。它是各种 kill 操作的核心。

数据流:输入范围和剪切类型 → 扩展范围以完整覆盖特殊元素,取出被删文字,存入 kill_buffer,再用 replace_range_raw 删除 → 文本和剪切板同步更新。

调用关系:kill_range 和 kill_line_range 都调用它;它会调用 expand_range_to_element_boundaries、store_kill_buffer、replace_range_raw。

调用图:调用 3 个内部函数(expand_range_to_element_boundaries, replace_range_raw, store_kill_buffer);被 2 处调用(kill_line_range, kill_range);外部调用 1 个(clone)。

TextArea::yank_range1105–1107 ↗
fn yank_range(&mut self, range: Range<usize>)

作用:复制一段字符级范围到剪切板,但不删除原文。Vim 的 y 加移动命令会用它。

数据流:输入范围 → 交给 yank_range_with_kind,标记为 Characterwise → 文本不变,剪切板可能更新。

调用关系:apply_vim_operator 调用它;实际复制逻辑在 yank_range_with_kind。

调用图:调用 1 个内部函数(yank_range_with_kind);被 2 处调用(apply_vim_operator, apply_vim_operator_to_range)。

TextArea::yank_line_range1109–1111 ↗
fn yank_line_range(&mut self, range: Range<usize>)

作用:复制一整行范围到剪切板,并标记为整行。这样之后粘贴会按行处理。

数据流:输入行范围 → 交给 yank_range_with_kind,标记为 Linewise → 文本不变,剪切板更新。

调用关系:yank_current_line 调用它;paste_after_cursor 根据保存的类型决定粘贴方式。

调用图:调用 1 个内部函数(yank_range_with_kind);被 1 处调用(yank_current_line)。

TextArea::yank_range_with_kind1113–1123 ↗
fn yank_range_with_kind(&mut self, range: Range<usize>, kind: KillBufferKind)

作用:复制指定范围到剪切板,并记录复制类型。它不会改变文本内容。

数据流:输入范围和类型 → 扩展范围以完整包含特殊元素,取出字符串,存入 kill_buffer 和类型 → 输出体现在内部剪切板状态上。

调用关系:yank_range 和 yank_line_range 调用它;它复用 expand_range_to_element_boundaries 和 store_kill_buffer。

调用图:调用 2 个内部函数(expand_range_to_element_boundaries, store_kill_buffer);被 2 处调用(yank_line_range, yank_range)。

TextArea::store_kill_buffer1125–1128 ↗
fn store_kill_buffer(&mut self, text: String, kind: KillBufferKind)

作用:保存最近一次删除或复制的文字,以及它是字符级还是整行级。后续粘贴要靠这份记录。

数据流:输入字符串和类型 → 覆盖 kill_buffer 与 kill_buffer_kind → 不改变可见文本。

调用关系:kill_range_with_kind 和 yank_range_with_kind 调用它;yank、paste_after_cursor 会读取保存结果。

调用图:被 2 处调用(kill_range_with_kind, yank_range_with_kind)。

TextArea::paste_after_cursor1130–1142 ↗
fn paste_after_cursor(&mut self)

作用:按 Vim 的 p 规则在光标后粘贴。字符级内容贴在下一个字符位置,整行内容贴到当前行之后。

数据流:读取 kill_buffer 和类型 → 空则不动;整行则调用 paste_line_after_current_line;字符级则移动到下一个安全边界后插入 → 文本变长。

调用关系:handle_vim_normal 处理 paste_after 时调用它;它依赖 next_atomic_boundary、set_cursor、insert_str。

调用图:调用 4 个内部函数(insert_str, next_atomic_boundary, paste_line_after_current_line, set_cursor);被 1 处调用(handle_vim_normal)。

TextArea::paste_line_after_current_line1144–1163 ↗
fn paste_line_after_current_line(&mut self)

作用:把整行剪切板内容粘贴到当前行下面,并处理末尾有没有换行的小差异。

数据流:读取当前行尾和 kill_buffer → 计算插入点、光标应落位置和需要补的换行 → insert_str_at 插入,再 set_cursor → 文本和光标更新。

调用关系:paste_after_cursor 在剪切板类型是 Linewise 时调用它。

调用图:调用 3 个内部函数(end_of_current_line, insert_str_at, set_cursor);被 1 处调用(paste_after_cursor);外部调用 1 个(format!)。

TextArea::yank_current_line1165–1168 ↗
fn yank_current_line(&mut self)

作用:复制当前整行到剪切板。Vim 的 yy 会走这里。

数据流:计算当前行含换行符的范围 → yank_line_range 保存 → 文本不变,剪切板更新。

调用关系:handle_vim_normal 和 handle_vim_operator 调用它;范围由 current_line_range_with_newline 提供。

调用图:调用 2 个内部函数(current_line_range_with_newline, yank_line_range);被 2 处调用(handle_vim_normal, handle_vim_operator)。

TextArea::kill_current_line1170–1173 ↗
fn kill_current_line(&mut self)

作用:删除当前整行,并按整行类型保存到剪切板。Vim 的 dd 或普通整行剪切会用它。

数据流:计算当前行含换行符的范围 → kill_line_range 删除并保存 → 文本变短,剪切板更新。

调用关系:handle_vim_operator 和 input_with_keymap 调用它;范围来自 current_line_range_with_newline。

调用图:调用 2 个内部函数(current_line_range_with_newline, kill_line_range);被 2 处调用(handle_vim_operator, input_with_keymap)。

TextArea::current_line_range_with_newline1175–1180 ↗
fn current_line_range_with_newline(&self) -> Range<usize>

作用:返回当前行的范围,并尽量把行尾换行符也包括进去。整行删除或复制时需要这个范围。

数据流:读取当前行首和行尾 → 如果行尾后还有内容,就把换行符也纳入范围 → 返回 Range。

调用关系:kill_current_line、yank_current_line 和 linewise_range_for_vertical_motion 都用它。

调用图:调用 2 个内部函数(beginning_of_current_line, end_of_current_line);被 3 处调用(kill_current_line, linewise_range_for_vertical_motion, yank_current_line)。

TextArea::move_cursor_left1183–1186 ↗
fn move_cursor_left(&mut self)

作用:把光标向左移动一个完整单位。不会停在 emoji 或特殊元素的中间。

数据流:读取 cursor_pos → 用 prev_atomic_boundary 找左侧安全位置 → 更新光标并清掉垂直移动记忆列。

调用关系:普通键位、Vim 普通模式和 target_for_motion 都会调用它。

调用图:调用 1 个内部函数(prev_atomic_boundary);被 3 处调用(handle_vim_normal, input_with_keymap, target_for_motion)。

TextArea::move_cursor_right1189–1192 ↗
fn move_cursor_right(&mut self)

作用:把光标向右移动一个完整单位。它把特殊元素当成一个整体跳过。

数据流:读取 cursor_pos → 用 next_atomic_boundary 找右侧安全位置 → 更新光标并清掉垂直移动记忆列。

调用关系:普通键位、Vim 普通模式和 target_for_motion 都会调用它。

调用图:调用 1 个内部函数(next_atomic_boundary);被 3 处调用(handle_vim_normal, input_with_keymap, target_for_motion)。

TextArea::move_cursor_up1194–1255 ↗
fn move_cursor_up(&mut self)

作用:把光标向上一行移动,并尽量保持原来的显示列。它优先按屏幕折行后的“可视行”移动。

数据流:读取换行缓存、光标和目标列 → 如果有折行信息就移动到上一条可视行同列;否则按真实换行符的逻辑行移动 → 更新光标和 preferred_col。

调用关系:input_with_keymap、handle_vim_normal 和 target_for_motion 调用它;它使用 current_display_col、wrapped_line_index_by_start、move_to_display_col_on_line。

调用图:调用 2 个内部函数(current_display_col, move_to_display_col_on_line);被 3 处调用(handle_vim_normal, input_with_keymap, target_for_motion);外部调用 1 个(wrapped_line_index_by_start)。

TextArea::move_cursor_down1257–1323 ↗
fn move_cursor_down(&mut self)

作用:把光标向下一行移动,并尽量保持原来的显示列。长行自动换行时,它按屏幕看到的行移动。

数据流:读取换行缓存、光标和目标列 → 有折行信息就去下一条可视行;没有就按换行符找下一逻辑行 → 更新光标和 preferred_col。

调用关系:input_with_keymap、handle_vim_normal 和 target_for_motion 调用它;逻辑和 move_cursor_up 对称。

调用图:调用 2 个内部函数(current_display_col, move_to_display_col_on_line);被 3 处调用(handle_vim_normal, input_with_keymap, target_for_motion);外部调用 1 个(wrapped_line_index_by_start)。

TextArea::move_cursor_to_beginning_of_line1325–1333 ↗
fn move_cursor_to_beginning_of_line(&mut self, move_up_at_bol: bool)

作用:把光标移到当前行开头;某些快捷键在已经处于行首时还会跳到上一行开头。

数据流:输入是否允许行首再上移 → 计算当前行首;如果允许且已在行首,就找上一行行首,否则设为当前行首 → 更新光标。

调用关系:input_with_keymap 处理行首快捷键时调用它;它使用 beginning_of_current_line、beginning_of_line、set_cursor。

调用图:调用 3 个内部函数(beginning_of_current_line, beginning_of_line, set_cursor);被 1 处调用(input_with_keymap)。

TextArea::move_cursor_to_end_of_line1335–1343 ↗
fn move_cursor_to_end_of_line(&mut self, move_down_at_eol: bool)

作用:把光标移到当前行尾;某些快捷键在已经处于行尾时还会跳到下一行行尾。

数据流:输入是否允许行尾再下移 → 计算当前行尾;如果允许且已在行尾,就找下一行行尾,否则设为当前行尾 → 更新光标。

调用关系:input_with_keymap 处理行尾快捷键时调用它;它依赖 end_of_current_line、end_of_line、set_cursor。

调用图:调用 3 个内部函数(end_of_current_line, end_of_line, set_cursor);被 1 处调用(input_with_keymap)。

TextArea::element_payloads1347–1352 ↗
fn element_payloads(&self) -> Vec<String>

作用:取出所有特殊元素当前对应的文本内容。比如图片占位符保存成文本时,可以列出来。

数据流:读取 elements 列表 → 对每个范围从 text 里切出字符串,范围无效就跳过 → 返回字符串列表。

调用关系:它是元素信息的查询接口;不改变文本框状态。

TextArea::text_elements1354–1368 ↗
fn text_elements(&self) -> Vec<UserTextElement>

作用:把内部特殊元素转换成对外使用的 UserTextElement 列表,包含字节范围和占位文本。

数据流:读取每个内部元素 → 根据 range 取出对应文本,包装成 UserTextElement → 返回列表。

调用关系:外层需要保存或传递输入框里的元素信息时会调用它。

TextArea::text_element_snapshots1370–1383 ↗
fn text_element_snapshots(&self) -> Vec<TextElementSnapshot>

作用:生成特殊元素的快照,带 id、范围和当前文本。快照适合后续比较或渲染辅助使用。

数据流:读取 elements 和 text → 对每个有效范围生成 TextElementSnapshot → 返回快照列表。

调用关系:它是内部查询接口;element_id_for_exact_range 等功能可配合它识别元素。

TextArea::element_id_for_exact_range1385–1390 ↗
fn element_id_for_exact_range(&self, range: Range<usize>) -> Option<u64>

作用:查找某个精确范围对应的特殊元素 id。调用者已知道范围时,可以用它确认是哪一个元素。

数据流:输入范围 → 遍历元素列表找 range 完全相等的一项 → 返回 id 或 None。

调用关系:这是元素定位工具;它不改变状态,只帮助外层把范围和元素身份关联起来。

TextArea::replace_element_payload1396–1459 ↗
fn replace_element_payload(&mut self, old: &str, new: &str) -> bool

作用:把某个特殊元素的占位文本从旧值替换成新值,并保持元素范围和后续元素位置正确。比如本地图片上传后改显示标签时会用到。

数据流:输入旧文本和新文本 → 找到文本完全匹配的元素,替换该范围内容,更新该元素范围,按长度差移动后面的元素和光标,清缓存并排序 → 返回是否替换成功。

调用关系:relabel_local_images 会调用它;它内部调用 clamp_pos_to_nearest_boundary 保证光标安全。

调用图:调用 1 个内部函数(clamp_pos_to_nearest_boundary);被 1 处调用(relabel_local_images)。

TextArea::insert_element1461–1469 ↗
fn insert_element(&mut self, text: &str) -> u64

作用:在当前光标处插入一个特殊元素文本,并把这段文本登记为不可随便拆开的元素。

数据流:输入元素显示文本 → 修正插入位置,插入文字,记录 start..end 元素范围并生成 id,把光标放到元素后面 → 返回元素 id。

调用关系:attach_image 会调用它;它组合了 insert_str_at、add_element 和 set_cursor。

调用图:调用 4 个内部函数(add_element, clamp_pos_for_insertion, insert_str_at, set_cursor);被 1 处调用(attach_image)。

TextArea::add_element1471–1476 ↗
fn add_element(&mut self, range: Range<usize>) -> u64

作用:把一个范围登记成内部特殊元素,并分配唯一 id。它不负责检查范围是否合法。

数据流:输入范围 → 获取下一个元素 id,压入 elements,并按起点排序 → 返回 id。

调用关系:insert_element 和 add_element_range 调用它;id 来自 next_element_id。

调用图:调用 1 个内部函数(next_element_id);被 2 处调用(add_element_range, insert_element)。

TextArea::add_element_range1482–1504 ↗
fn add_element_range(&mut self, range: Range<usize>) -> Option<u64>

作用:把已有文本中的一段范围标记为特殊元素,同时检查范围不能为空、重复或与别的元素重叠。

数据流:输入范围 → 修正到字符边界和文本长度内,检查有效性、重复和重叠 → 成功则 add_element 并返回 id,失败返回 None。

调用关系:外层想把已有文本片段变成元素时调用它;它依赖 clamp_pos_to_char_boundary 和 add_element。

调用图:调用 2 个内部函数(add_element, clamp_pos_to_char_boundary)。

TextArea::remove_element_range1506–1516 ↗
fn remove_element_range(&mut self, range: Range<usize>) -> bool

作用:取消某个精确范围的特殊元素标记。文字本身不会被删除,只是不再被当成整体保护。

数据流:输入范围 → 修正到字符边界,找完全相同范围的元素并移除 → 返回是否真的移除了。

调用关系:这是元素维护接口;它只改 elements,不改 text。

调用图:调用 1 个内部函数(clamp_pos_to_char_boundary)。

TextArea::next_element_id1518–1522 ↗
fn next_element_id(&mut self) -> u64

作用:生成下一个特殊元素 id。每个元素有 id,方便后续识别同一个元素。

数据流:读取 next_element_id → 返回当前值,并把内部计数加 1,溢出时饱和处理 → 更新计数器。

调用关系:set_text_inner 和 add_element 会调用它给新元素编号。

调用图:被 2 处调用(add_element, set_text_inner)。

TextArea::find_element_containing1523–1527 ↗
fn find_element_containing(&self, pos: usize) -> Option<usize>

作用:判断某个位置是否落在特殊元素内部。这里的内部不包括元素两端边界。

数据流:输入位置 → 遍历元素范围,找 start < pos < end 的元素 → 返回元素索引或 None。

调用关系:光标修正、插入修正、左右移动边界和词移动都会用它避免进入元素中间。

调用图:被 5 处调用(adjust_pos_out_of_elements, clamp_pos_for_insertion, clamp_pos_to_nearest_boundary, next_atomic_boundary, prev_atomic_boundary)。

TextArea::clamp_pos_to_char_boundary1529–1547 ↗
fn clamp_pos_to_char_boundary(&self, pos: usize) -> usize

作用:把一个字节位置修正到最近的合法字符边界。Rust 字符串按字节存,但中文和 emoji 不能从中间切。

数据流:输入位置 → 限制在文本长度内;如果不是字符边界,就找前后最近边界并选更近的 → 返回安全位置。

调用关系:设置文本、添加元素、移除元素、设置光标等许多函数都依赖它打底。

调用图:被 5 处调用(add_element_range, clamp_pos_for_insertion, clamp_pos_to_nearest_boundary, remove_element_range, set_text_inner)。

TextArea::clamp_pos_to_nearest_boundary1549–1563 ↗
fn clamp_pos_to_nearest_boundary(&self, pos: usize) -> usize

作用:把位置修正到既是字符边界、又不在特殊元素内部的位置。若落在元素中间,就靠近哪边贴到哪边。

数据流:输入位置 → 先修正到字符边界,再检查是否在元素内部;如果在,就比较到元素两端距离并返回最近端点 → 输出安全位置。

调用关系:set_cursor、replace_range_raw、replace_element_payload、move_to_display_col_on_line 等用它保护光标。

调用图:调用 2 个内部函数(clamp_pos_to_char_boundary, find_element_containing);被 5 处调用(move_to_display_col_on_line, replace_element_payload, replace_range_raw, set_cursor, set_text_inner)。

TextArea::clamp_pos_for_insertion1565–1581 ↗
fn clamp_pos_for_insertion(&self, pos: usize) -> usize

作用:把插入位置修正到安全地方,特别是不允许把新文字插进特殊元素中间。

数据流:输入位置 → 修正到字符边界;如果在元素内部,就贴到较近的元素起点或终点 → 返回可插入位置。

调用关系:insert_str_at 和 insert_element 调用它;它和 clamp_pos_to_nearest_boundary 逻辑相近,但专门服务插入。

调用图:调用 2 个内部函数(clamp_pos_to_char_boundary, find_element_containing);被 2 处调用(insert_element, insert_str_at)。

TextArea::expand_range_to_element_boundaries1583–1603 ↗
fn expand_range_to_element_boundaries(&self, mut range: Range<usize>) -> Range<usize>

作用:把一个编辑范围扩展到完整包含所有碰到的特殊元素。这样删除、替换、复制不会只处理元素的一半。

数据流:输入范围 → 反复检查所有元素,只要范围和元素相交,就扩大到包含整个元素 → 返回最终范围。

调用关系:replace_range、kill_range_with_kind、yank_range_with_kind 都用它保证元素整体性。

调用图:被 3 处调用(kill_range_with_kind, replace_range, yank_range_with_kind)。

TextArea::shift_elements1605–1628 ↗
fn shift_elements(&mut self, at: usize, removed: usize, inserted: usize)

作用:文本插入或删除后,移动特殊元素的范围,让它们继续指向正确的文字。就像书里插了一页后,后面页码都要加一。

数据流:输入编辑起点、删除长度、插入长度 → 删除被完全覆盖的元素,按长度差平移后方元素,重叠元素则尽量修正到新边界 → 更新 elements。

调用关系:insert_str_at 和 update_elements_after_replace 调用它;它是文本变化后维护元素位置的核心。

调用图:被 2 处调用(insert_str_at, update_elements_after_replace)。

TextArea::update_elements_after_replace1630–1632 ↗
fn update_elements_after_replace(&mut self, start: usize, end: usize, inserted_len: usize)

作用:在一段文本被替换后更新元素范围。它是 replace_range_raw 里更好读的一层包装。

数据流:输入替换起点、终点和插入长度 → 计算删除长度并调用 shift_elements → elements 被调整。

调用关系:replace_range_raw 调用它;实际细节由 shift_elements 完成。

调用图:调用 1 个内部函数(shift_elements);被 1 处调用(replace_range_raw)。

TextArea::prev_atomic_boundary1634–1658 ↗
fn prev_atomic_boundary(&self, pos: usize) -> usize

作用:找到当前位置左边的上一个“不可拆单位”边界。不可拆单位可以是一个 Unicode 字形,也可以是一个特殊元素整体。

数据流:输入位置 → 如果在元素尾部或内部,直接跳到元素起点;否则用 Unicode 字形边界往前找,再避开元素内部 → 返回左侧安全位置。

调用关系:退格、左移、Vim 退出插入模式、词尾光标计算等都会调用它。

调用图:调用 1 个内部函数(find_element_containing);被 7 处调用(delete_backward, handle_vim_insert, move_cursor_left, vim_line_end_cursor, vim_normal_end_cursor, vim_word_end_cursor, vim_word_end_exclusive);外部调用 1 个(new)。

TextArea::next_atomic_boundary1660–1684 ↗
fn next_atomic_boundary(&self, pos: usize) -> usize

作用:找到当前位置右边的下一个完整单位边界。它保证右移或前删不会切开组合字符或特殊元素。

数据流:输入位置 → 如果在元素起点或内部,直接跳到元素终点;否则用 Unicode 字形边界往后找,再避开元素内部 → 返回右侧安全位置。

调用关系:delete_forward、move_cursor_right、paste_after_cursor 和 Vim 普通模式移动会调用它。

调用图:调用 1 个内部函数(find_element_containing);被 5 处调用(delete_forward, delete_forward_kill, handle_vim_normal, move_cursor_right, paste_after_cursor);外部调用 1 个(new)。

TextArea::beginning_of_previous_word1686–1719 ↗
fn beginning_of_previous_word(&self) -> usize

作用:找到光标前一个词的开头。按词左移或向后删词时会用它。

数据流:读取光标前文本 → 跳过尾部空白,找到连续非空白片段,按分隔符切成小片,从后往前确定词首 → 若落入元素则挪到元素起点 → 返回位置。

调用关系:delete_backward_word、input_with_keymap、handle_vim_normal、target_for_motion 都会调用它;它使用 split_word_pieces。

调用图:调用 2 个内部函数(adjust_pos_out_of_elements, split_word_pieces);被 4 处调用(delete_backward_word, handle_vim_normal, input_with_keymap, target_for_motion)。

TextArea::end_of_next_word1721–1723 ↗
fn end_of_next_word(&self) -> usize

作用:找到从当前光标开始的下一个词结尾。按词右移或向前删词时会用它。

数据流:读取当前 cursor_pos → 调用 end_of_next_word_from → 返回词尾位置。

调用关系:beginning_of_next_word、delete_forward_word、input_with_keymap、vim_word_end_exclusive 都会间接或直接用它。

调用图:调用 1 个内部函数(end_of_next_word_from);被 4 处调用(beginning_of_next_word, delete_forward_word, input_with_keymap, vim_word_end_exclusive)。

TextArea::end_of_next_word_from1725–1749 ↗
fn end_of_next_word_from(&self, cursor_pos: usize) -> usize

作用:从指定位置开始,找到下一个词的结束位置。它支持从任意位置继续找,不只限当前光标。

数据流:输入起始位置 → 跳过空白,取下一个非空白连续片段,按分隔符切分并确定结尾;如果结尾在元素内部则挪到元素终点 → 返回位置。

调用关系:end_of_next_word 和 vim_word_end_exclusive 调用它;切词依赖 split_word_pieces。

调用图:调用 2 个内部函数(adjust_pos_out_of_elements, split_word_pieces);被 2 处调用(end_of_next_word, vim_word_end_exclusive)。

TextArea::vim_word_end_exclusive1751–1763 ↗
fn vim_word_end_exclusive(&self) -> usize

作用:按 Vim 规则计算“词尾之后的位置”。这个位置适合拿来做范围结束点,比如删除到词尾。

数据流:读取当前光标 → 找下一个词尾;若按 Vim 光标规则会停在原地,则继续找下一个词 → 返回用于范围的结束位置。

调用关系:target_for_motion 和 vim_word_end_cursor 调用它;它结合 end_of_next_word、end_of_next_word_from 和 prev_atomic_boundary。

调用图:调用 3 个内部函数(end_of_next_word, end_of_next_word_from, prev_atomic_boundary);被 2 处调用(target_for_motion, vim_word_end_cursor)。

TextArea::vim_word_end_cursor1765–1772 ↗
fn vim_word_end_cursor(&self) -> usize

作用:按 Vim 普通模式的光标规则,算出按 e 后光标应该停在哪个字符上。

数据流:先算词尾后的范围位置 → 如果在当前光标后,就退到前一个完整单位;否则原样返回 → 得到可显示的词尾光标位置。

调用关系:handle_vim_normal 处理移动到词尾时调用它;它依赖 vim_word_end_exclusive。

调用图:调用 2 个内部函数(prev_atomic_boundary, vim_word_end_exclusive);被 1 处调用(handle_vim_normal)。

TextArea::vim_line_end_cursor1774–1782 ↗
fn vim_line_end_cursor(&self) -> usize

作用:按 Vim 规则计算当前行尾光标位置。普通模式下通常停在行内最后一个字符,而不是换行符后。

数据流:读取当前行首和行尾 → 如果行里有内容,就从行尾退到前一个完整单位且不越过行首;空行则返回行尾 → 输出光标位置。

调用关系:handle_vim_normal 处理行尾移动时调用它;它使用 prev_atomic_boundary。

调用图:调用 3 个内部函数(beginning_of_current_line, end_of_current_line, prev_atomic_boundary);被 1 处调用(handle_vim_normal)。

TextArea::beginning_of_next_word1784–1801 ↗
fn beginning_of_next_word(&self) -> usize

作用:找到下一个词的开头。Vim 的 w 或普通按词右移会用它。

数据流:从当前光标往后找非空白;如果当前在空白中,返回那个词首;如果已在词中,先到当前词尾,再找下一个非空白 → 避开元素内部后返回。

调用关系:handle_vim_normal 和 target_for_motion 调用它;它会用 end_of_next_word 辅助跨过当前词。

调用图:调用 2 个内部函数(adjust_pos_out_of_elements, end_of_next_word);被 2 处调用(handle_vim_normal, target_for_motion)。

TextArea::adjust_pos_out_of_elements1803–1814 ↗
fn adjust_pos_out_of_elements(&self, pos: usize, prefer_start: bool) -> usize

作用:如果某个计算出的位置落在特殊元素里面,就把它挪到元素起点或终点。它保护词移动不会停在元素中间。

数据流:输入位置和偏好方向 → 查找是否在元素内部;如果在,按 prefer_start 返回元素起点或终点,否则返回原位置。

调用关系:beginning_of_next_word、beginning_of_previous_word、end_of_next_word_from 都用它修正词边界。

调用图:调用 1 个内部函数(find_element_containing);被 3 处调用(beginning_of_next_word, beginning_of_previous_word, end_of_next_word_from)。

TextArea::wrapped_lines1817–1836 ↗
fn wrapped_lines(&self, width: u16) -> Ref<'_, Vec<Range<usize>>>

作用:按给定宽度计算文本会折成哪些屏幕行,并缓存结果。终端宽度不够时,长文本需要这样分行显示。

数据流:输入宽度 → 如果缓存不存在或宽度变了,就重新调用 wrapping 算出每行字节范围并保存;然后返回缓存里的行范围引用。

调用关系:desired_height、cursor_pos_with_state 和各类 render 函数都调用它;文本变化时其他函数会清掉 wrap_cache。

调用图:调用 1 个内部函数(wrap_ranges);被 5 处调用(cursor_pos_with_state, desired_height, render_ref, render_ref_masked, render_ref_styled_with_highlights);外部调用 2 个(new, map)。

TextArea::effective_scroll1843–1869 ↗
fn effective_scroll(
        &self,
        area_height: u16,
        lines: &[Range<usize>],
        current_scroll: u16,
    ) -> u16

作用:计算实际应该滚动到哪一行,保证光标尽量在可见区域内。用户输入多行时,不会把光标滚出屏幕。

数据流:输入区域高度、折行列表和当前滚动值 → 限制滚动不超过最大值,再根据光标所在行向上或向下修正 → 返回新的滚动行号。

调用关系:cursor_pos_with_state 和 render_ref 系列函数都用它,让渲染和光标坐标使用同一滚动规则。

调用图:被 4 处调用(cursor_pos_with_state, render_ref, render_ref_masked, render_ref_styled_with_highlights);外部调用 2 个(wrapped_line_index_by_start, len)。

TextArea::render_ref1882–1890 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State)

作用:把文本框内容画到终端缓冲区里,使用默认样式。这里的缓冲区可以理解成即将显示到屏幕上的字符网格。

数据流:输入显示区域、缓冲区和状态 → 计算折行和有效滚动,更新 state.scroll,取可见行范围并调用 render_lines 绘制 → 屏幕缓冲区被写入。

调用关系:这是组件渲染入口之一;它依赖 wrapped_lines、effective_scroll 和 render_lines。

调用图:调用 3 个内部函数(effective_scroll, render_lines, wrapped_lines);外部调用 1 个(default)。

TextArea::render_ref_masked1894–1908 ↗
fn render_ref_masked(
        &self,
        area: Rect,
        buf: &mut Buffer,
        state: &mut TextAreaState,
        mask_char: char,
    )

作用:把文本框画出来,但用指定字符遮住真实内容。密码输入框这类场景会用到。

数据流:输入区域、缓冲区、滚动状态和遮罩字符 → 计算折行与滚动,更新状态,再调用 render_lines_masked 绘制可见部分 → 缓冲区显示遮罩内容。

调用关系:这是带遮罩的渲染接口;它和普通 render_ref 使用同样的滚动与折行逻辑。

调用图:调用 3 个内部函数(effective_scroll, render_lines_masked, wrapped_lines)。

TextArea::render_ref_styled_with_highlights1914–1929 ↗
fn render_ref_styled_with_highlights(
        &self,
        area: Rect,
        buf: &mut Buffer,
        state: &mut TextAreaState,
        base_style: Style,
        highlights: &[(Range<usize>, St

作用:用基础样式和高亮范围绘制文本框。比如某些词需要变色提示时会用到。

数据流:输入区域、缓冲区、状态、基础样式和高亮列表 → 计算折行与滚动,更新 state.scroll,调用 render_lines 绘制带样式的可见行 → 缓冲区被写入。

调用关系:这是带样式和高亮的渲染接口;它复用 wrapped_lines、effective_scroll 和 render_lines。

调用图:调用 3 个内部函数(effective_scroll, render_lines, wrapped_lines)。

TextArea::render_lines1931–1975 ↗
fn render_lines(
        &self,
        area: Rect,
        buf: &mut Buffer,
        lines: &[Range<usize>],
        range: std::ops::Range<usize>,
        base_style: Style,
        highlights: &[(R

作用:把文本输入框当前可见的几行画到终端缓冲区里,并按需要加上普通样式、特殊元素样式和搜索高亮。它保证用户看到的文字、附件占位符和临时高亮不会互相盖错。

数据流:输入是屏幕区域、要写入的缓冲区、已经切好的行范围、要显示哪几行、基础样式和高亮范围;它逐行取出文本,先用基础样式填底并写文字,再把特殊元素覆盖成青色,最后把高亮覆盖上去;输出不是返回值,而是缓冲区被改成可显示的画面。

调用关系:当外层渲染函数 render_ref 或 render_ref_styled_with_highlights 需要真正画普通文本时,会把最后一步交给它。它内部调用缓冲区的 set_style、set_string 等方法,把计算好的内容落到屏幕格子上。

调用图:被 2 处调用(render_ref, render_ref_styled_with_highlights);外部调用 5 个(set_string, set_style, new, fg, enumerate)。

TextArea::render_lines_masked1977–1995 ↗
fn render_lines_masked(
        &self,
        area: Rect,
        buf: &mut Buffer,
        lines: &[Range<usize>],
        range: std::ops::Range<usize>,
        mask_char: char,
    )

作用:把可见文本画成一串掩码字符,比如密码输入框里显示星号而不显示真实内容。这样既能保留长度和换行效果,又不会泄露原文。

数据流:输入是屏幕区域、缓冲区、行范围、要显示的行和掩码字符;它逐行读取真实文本,但把每个字符都替换成指定掩码;结果是缓冲区里出现掩码文本,原始文本本身不变。

调用关系:它被 render_ref_masked 调用,专门服务于“隐藏真实内容”的渲染路径。它不像 render_lines 那样叠加元素和高亮,只做简单的遮盖显示。

调用图:被 1 处调用(render_ref_masked);外部调用 3 个(set_string, default, enumerate)。

tests::rand_grapheme2006–2045 ↗
fn rand_grapheme(rng: &mut rand::rngs::StdRng) -> String

作用:随机生成一个“用户眼里的一格文字”,可能是普通字母、中文、emoji,或者带重音的组合字符。它用来测试输入框面对复杂 Unicode 字符时是否稳定。

数据流:输入是一个随机数生成器;它按概率挑选换行、空格、字母、数字、emoji、中文、组合字符等;输出是一个字符串,代表一个可插入测试文本里的字符单元。

调用关系:这是测试辅助函数,测试代码会用它制造各种难处理的文本。它依赖随机范围选择和格式化字符串,不参与正式运行时的输入框渲染。

调用图:外部调用 2 个(random_range, format!)。

tests::ta_with2047–2051 ↗
fn ta_with(text: &str) -> TextArea

作用:快速创建一个已经填好指定文字的 TextArea,方便测试少写重复准备代码。它相当于测试里的“造一个输入框样品”。

数据流:输入是一段字符串;它新建 TextArea,然后把字符串插进去;输出是带有这段文字的 TextArea。

调用关系:很多测试都会先调用它准备场景,然后再测试插入、删除、移动光标或 Vim 操作。它内部只依赖 TextArea::new 和插入文本能力。

调用图:调用 1 个内部函数(new)。

tests::insert_and_replace_update_cursor_and_text2054–2094 ↗
fn insert_and_replace_update_cursor_and_text()

作用:检查插入和替换文字后,文本内容和光标位置是否一起正确更新。没有这类保证,用户打字后光标可能跳到奇怪位置。

数据流:它创建不同文本,设置光标,执行 insert_str、insert_str_at 和 replace_range;然后比较实际文本和光标;结果是测试通过或失败,不产生业务输出。

调用关系:测试运行器调用它来验证基础编辑能力。它主要依赖 ta_with 准备 TextArea,并用断言检查 TextArea 的行为。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::insert_str_at_clamps_to_char_boundary2097–2104 ↗
fn insert_str_at_clamps_to_char_boundary()

作用:确认在中文这类多字节字符中间插入时,位置会被修正到合法字符边界。这样不会把一个字切坏。

数据流:它先插入“你”,再请求在字节位置 1 插入 A;代码会把位置夹到安全边界;最终文本应为“A你”,光标在 A 后面。

调用关系:这是测试运行器执行的边界测试。它直接创建 TextArea,检查 insert_str_at 对 Unicode 边界的保护。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

tests::set_text_clamps_cursor_to_char_boundary2107–2115 ↗
fn set_text_clamps_cursor_to_char_boundary()

作用:检查整段替换文本后,旧光标位置如果不再合法,会被移到安全位置。这样下一次输入不会破坏中文等多字节字符。

数据流:它先让光标在旧文本里,然后把文本换成“你”;光标被修正到 0;再插入 a,结果应正确出现在前面。

调用关系:测试运行器用它覆盖 set_text_clearing_elements 的安全行为。它验证设置文本和随后插入文本能顺利衔接。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

tests::delete_backward_and_forward_edges2118–2141 ↗
fn delete_backward_and_forward_edges()

作用:检查向前删和向后删在开头、结尾这些边界位置不会出错。比如光标在开头按退格键应该什么都不删。

数据流:它准备 abc,移动光标后执行 delete_backward 和 delete_forward;每一步都检查文本和光标;最终确认边界操作是安全的。

调用关系:这是基础删除行为的测试。它通过 ta_with 准备文本,直接调用删除函数。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::delete_forward_deletes_element_at_left_edge2144–2156 ↗
fn delete_forward_deletes_element_at_left_edge()

作用:确认光标正好在特殊元素左边时,向前删除会把整个元素删掉,而不是只删一部分。特殊元素像一个不可拆开的附件占位符。

数据流:它插入普通字母、一个元素和另一个字母;把光标放在元素起点;执行向前删除;结果文本只剩普通字母,光标保持在元素原来的位置。

调用关系:测试运行器用它检查 TextArea 对 atomic element(不可拆分元素)的删除规则。它直接查看元素范围并调用 delete_forward。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

tests::vim_insert_and_escape2159–2170 ↗
fn vim_insert_and_escape()

作用:检查开启 Vim 模式后,按 i 能进入插入,输入字符后按 Esc 能回到普通模式。Vim 模式是一套常见的键盘编辑习惯。

数据流:它创建空输入框,开启 Vim,发送 i、h、Esc 三个按键;文本变成 h,模式显示 Normal,光标回到字符上;测试用断言确认这些结果。

调用关系:测试运行器调用它验证 Vim 输入流程。它通过 TextArea::input 模拟真实按键。

调用图:调用 1 个内部函数(new);外部调用 3 个(Char, new, assert_eq!)。

tests::vim_insert_key_enters_insert_mode2173–2182 ↗
fn vim_insert_key_enters_insert_mode()

作用:确认键盘上的 Insert 键也能让 Vim 模式进入插入状态。这样不同键盘习惯的用户都能正常输入。

数据流:它开启 Vim,发送 Insert 键,再发送 h;如果进入插入模式成功,文本会包含 h,模式标签是 Insert。

调用关系:这是 Vim 键位兼容性测试。它把按键事件交给 TextArea::input。

调用图:调用 1 个内部函数(new);外部调用 3 个(Char, new, assert_eq!)。

tests::vim_normal_arrow_keys_move_cursor2185–2201 ↗
fn vim_normal_arrow_keys_move_cursor()

作用:检查 Vim 普通模式下,方向键仍然可以移动光标。即使用 Vim,也不强迫用户只用 hjkl。

数据流:它准备两行文本,把光标放在指定位置;依次发送右、下、左、上方向键;每次检查光标是否到达预期位置。

调用关系:测试运行器用它确认普通模式移动逻辑。它依赖 ta_with 和按键输入入口。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::vim_escape_from_insert_at_start_does_not_underflow2204–2213 ↗
fn vim_escape_from_insert_at_start_does_not_underflow()

作用:确认在文本开头退出插入模式时,光标不会往左减到负数。负数位置在程序里可能造成严重错误。

数据流:它开启 Vim,输入 z,再按 Esc;模式变 Normal,光标安全停在 0;断言负责验证。

调用关系:这是 Vim 退出插入模式的边界测试。它通过 input 模拟用户按键。

调用图:调用 1 个内部函数(new);外部调用 3 个(Char, new, assert_eq!)。

tests::vim_escape_from_insert_at_line_start_stays_on_line2216–2226 ↗
fn vim_escape_from_insert_at_line_start_stays_on_line()

作用:检查在一行开头按 Esc 退出插入模式时,光标不会跳到上一行。用户会期望它还停在当前行开头。

数据流:它准备两行文本,把光标放到第二行开头;进入插入后按 Esc;结果模式变 Normal,光标仍在第二行开头。

调用关系:测试运行器用它保护 Vim 光标规则。它依赖 ta_with 和按键事件。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_escape_moves_by_grapheme_boundary2229–2239 ↗
fn vim_escape_moves_by_grapheme_boundary()

作用:检查从 emoji 后面退出插入模式时,光标按完整字符移动,而不是停在 emoji 内部。这里的 grapheme 指用户眼里一个完整字符。

数据流:它准备两个点赞 emoji,让光标在末尾;进入插入再按 Esc;光标应退到第一个 emoji 后,也就是第二个 emoji 上的位置。

调用关系:这是 Unicode 光标安全测试。它验证 Vim 退出插入模式时也尊重字符边界。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_escape_respects_atomic_element_boundary2242–2253 ↗
fn vim_escape_respects_atomic_element_boundary()

作用:确认 Vim 退出插入模式时,不会把光标放进不可拆分元素内部。元素必须像一个整体被跨过。

数据流:它插入 a 和一个元素,开启 Vim,进入插入再退出;结果光标停在元素前的合法边界。

调用关系:测试运行器用它检查 Vim 光标规则和特殊元素规则能一起工作。

调用图:调用 1 个内部函数(new);外部调用 3 个(Char, new, assert_eq!)。

tests::vim_shift_i_enters_insert_at_first_non_blank_with_shift_only_binding2256–2266 ↗
fn vim_shift_i_enters_insert_at_first_non_blank_with_shift_only_binding()

作用:检查大写 I 这个 Vim 操作能跳到当前行第一个非空格字符处并进入插入模式。它还验证只绑定 Shift 形式时也能识别。

数据流:它准备带缩进的文本,改写 Vim 键位映射,把光标放在行中;发送大写 I;结果进入 Insert,光标到缩进之后。

调用关系:这是 Vim 键位映射测试。它设置 vim_normal_keymap 后,通过 input 验证分发结果。

调用图:外部调用 5 个(Char, new, assert_eq!, ta_with, vec!)。

tests::vim_shift_a_enters_insert_at_line_end_with_shift_only_binding2269–2279 ↗
fn vim_shift_a_enters_insert_at_line_end_with_shift_only_binding()

作用:检查大写 A 能跳到行尾并进入插入模式,同时确认 Shift-only 绑定能匹配。Vim 用户常用它在行尾追加内容。

数据流:它准备两行文本,配置 append_line_end 的键位,发送大写 A;结果模式是 Insert,光标到当前行结尾。

调用关系:测试运行器用它覆盖 Vim 自定义键位。它通过输入事件触发 TextArea 的 Vim 普通模式处理。

调用图:外部调用 5 个(Char, new, assert_eq!, ta_with, vec!)。

tests::vim_shift_c_changes_to_line_end_and_enters_insert_mode2282–2293 ↗
fn vim_shift_c_changes_to_line_end_and_enters_insert_mode()

作用:检查 Shift+C 会删除从光标到行尾的内容,并进入插入模式。这是 Vim 的“改到行尾”。

数据流:它准备 hello world,光标放在 world 前;发送带 Shift 的 c;结果 world 被删进 kill_buffer,文本保留换行,光标不乱动并进入 Insert。

调用关系:这是 Vim 改写操作测试。它通过 input 走完整按键处理,而不是直接调用删除函数。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_uppercase_c_changes_to_line_end2296–2306 ↗
fn vim_uppercase_c_changes_to_line_end()

作用:确认直接收到大写 C 时,也能执行“改到行尾”。不同终端可能用不同方式表示 Shift 字母。

数据流:它准备文本,光标放在行中,发送 Char('C');结果行尾内容被删除,模式进入 Insert,光标停在删除起点。

调用关系:测试运行器用它补充 Shift+C 的兼容场景。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_s_substitutes_current_character_and_enters_insert_mode2309–2325 ↗
fn vim_s_substitutes_current_character_and_enters_insert_mode()

作用:检查 Vim 的 s 命令会删掉当前字符并进入插入模式。用户随后输入的新字会替换原字符。

数据流:它在 abc 的 b 上按 s;文本变 ac,进入 Insert;再输入 X 后文本变 aXc,光标随输入前进。

调用关系:这是 Vim 单字符替换流程测试。它通过连续 input 模拟真实操作。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_s_on_empty_line_enters_insert_without_deleting_newline2328–2338 ↗
fn vim_s_on_empty_line_enters_insert_without_deleting_newline()

作用:确认在空行上按 s 不会把换行符删掉,只是进入插入模式。否则空行结构会被意外破坏。

数据流:它准备中间有空行的文本,把光标放在空行;按 s 后文本不变,光标不变,模式进入 Insert。

调用关系:测试运行器用它保护 Vim 替换命令在空行上的边界行为。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_d_at_line_end_does_not_remove_newline2341–2351 ↗
fn vim_d_at_line_end_does_not_remove_newline()

作用:检查在行尾按 D 不会删除换行符。Vim 里 D 是删到行尾,但行尾已经没有普通字符时应不动。

数据流:它把光标放在 hello 的行尾,发送 D;文本不变,模式保持 Normal,kill_buffer 为空。

调用关系:这是 Vim 删除到行尾的边界测试。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_c_at_line_end_enters_insert_without_removing_newline2354–2365 ↗
fn vim_c_at_line_end_enters_insert_without_removing_newline()

作用:检查在行尾按 C 会进入插入模式,但不会删掉下一行。这样用户可以在行尾继续输入。

数据流:它在第一行行尾发送 C;文本不变,模式变 Insert,光标停在行尾,kill_buffer 为空。

调用关系:测试运行器用它验证 Vim 改到行尾的空范围行为。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_shift_o_opens_line_above_with_shift_only_binding2368–2379 ↗
fn vim_shift_o_opens_line_above_with_shift_only_binding()

作用:检查大写 O 能在当前行上方新开一行并进入插入模式。它还验证 Shift-only 键位绑定。

数据流:它准备两行文本,配置 open_line_above,光标在第二行;发送大写 O;文本中间多出空行,光标在新行开始。

调用关系:这是 Vim 开新行命令和键位映射的测试。

调用图:外部调用 5 个(Char, new, assert_eq!, ta_with, vec!)。

tests::vim_o_opens_line_below_on_inserted_line2382–2392 ↗
fn vim_o_opens_line_below_on_inserted_line()

作用:检查小写 o 能在当前行下方插入新空行,并把光标放到新行。Vim 用户常用它快速开下一行。

数据流:它准备 one/two 两行,在第一行按 o;文本变成中间有空行,模式进入 Insert,光标在新空行。

调用关系:测试运行器通过 input 验证 Vim 普通模式里的开行命令。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_o_opens_line_below_final_line_and_moves_to_new_line2395–2405 ↗
fn vim_o_opens_line_below_final_line_and_moves_to_new_line()

作用:确认在最后一行按 o 时,也能在末尾创建新行并移动过去。末尾没有原本的换行也要处理好。

数据流:它准备单行 one,按 o;文本变成 one 后跟换行,模式为 Insert,光标到新行开头。

调用关系:这是 Vim 开下方行的末尾边界测试。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_delete_word2408–2418 ↗
fn vim_delete_word()

作用:检查 Vim 的 dw 会删除从光标开始到下一个词前的内容。这里还要把删除内容存进 kill_buffer,方便粘贴。

数据流:它准备 hello world,光标在开头;依次输入 d 和 w;结果 hello 加空格被删除,kill_buffer 保存这段内容。

调用关系:这是 Vim 操作符加动作的测试。d 先进入待执行状态,w 决定删除范围。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_change_inner_word_deletes_word_and_enters_insert2421–2434 ↗
fn vim_change_inner_word_deletes_word_and_enters_insert()

作用:检查 ciw 会删除当前词内部并进入插入模式。ciw 是 Vim 里“改当前词”的常见组合。

数据流:它把光标放在 world 上,输入 c、i、w;world 被删入 kill_buffer,光标留在原处,模式进入 Insert。

调用关系:测试运行器用它验证 Vim 文本对象机制,也就是 i w 这种用来选中一块文字的规则。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_word_text_objects_cover_delete_yank_and_big_word2437–2460 ↗
fn vim_word_text_objects_cover_delete_yank_and_big_word()

作用:检查 Vim 的词文本对象既能复制也能删除,并区分普通 word 和大写 W 的“大词”。大词通常把标点也算进同一块。

数据流:第一段用 yaw 复制 hello 加空格,文本不变但 kill_buffer 有内容;第二段用 diW 删除 foo.bar/baz;结果文本和 kill_buffer 符合预期。

调用关系:这是 Vim 文本对象的综合测试。它通过 y、d 这些操作符和 a/i、w/W 这些对象组合来覆盖不同路径。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_word_text_objects_accept_cursor_at_word_end2463–2487 ↗
fn vim_word_text_objects_accept_cursor_at_word_end()

作用:确认光标正好在词尾时,word 文本对象仍然能选中刚结束的词。否则用户在词尾执行命令会失效。

数据流:它先在 hello 末尾执行 daw,删除 hello 加空格;再在文本末尾执行 ciW,删除 bar 并进入 Insert;断言检查文本、缓冲和光标。

调用关系:测试运行器用它覆盖 Vim 文本对象的边界位置。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_delimiter_text_objects_select_innermost_pair_and_aliases2490–2513 ↗
fn vim_delimiter_text_objects_select_innermost_pair_and_aliases()

作用:检查括号、方括号这类分隔符文本对象能选中最近的一对,并支持别名。比如 cib 表示改括号里面。

数据流:它先在嵌套括号里执行 cib,删除最内层内容;再对方括号执行 da],删除整对方括号及内容;结果写入 kill_buffer。

调用关系:这是 Vim 分隔符文本对象测试,验证 TextArea 能找到正确的左右边界。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_empty_inner_text_objects_are_valid_targets2516–2542 ↗
fn vim_empty_inner_text_objects_are_valid_targets()

作用:确认空括号或空引号内部也算合法目标。即使没有内容可删,命令也应进入插入模式而不是报错。

数据流:它分别准备 call() 和空引号文本;执行 ci( 或 ci";文本不变,kill_buffer 为空,光标在内部,模式进入 Insert。

调用关系:测试运行器用它保护 Vim 文本对象的空范围行为。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_quote_text_objects_are_line_local_and_handle_escapes2545–2568 ↗
fn vim_quote_text_objects_are_line_local_and_handle_escapes()

作用:检查引号文本对象只在当前行内寻找配对引号,并能跳过被反斜杠转义的引号。这样不会把字符串范围找错。

数据流:第一段在含转义引号的字符串里执行 ci",只删真正引号里的内容;第二段跨行引号里执行 di",因为不能跨行匹配所以不改文本。

调用关系:这是 Vim 引号文本对象的细节测试,关注转义字符和换行边界。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_text_object_cancellation_and_unsupported_change_motions_do_not_edit2571–2592 ↗
fn vim_text_object_cancellation_and_unsupported_change_motions_do_not_edit()

作用:检查不支持的 Vim 组合或按 Esc 取消时,不会误删文字。用户按错键时输入框应该安全退出待命状态。

数据流:它先输入 c 后接 $,确认文本不变并退出待执行状态;再输入 d i 后按 Esc,也确认文本和 kill_buffer 不变。

调用关系:测试运行器用它验证 Vim 操作符待命状态的取消逻辑。

调用图:外部调用 5 个(Char, new, assert!, assert_eq!, ta_with)。

tests::vim_operator_invalid_motion_is_consumed2595–2609 ↗
fn vim_operator_invalid_motion_is_consumed()

作用:确认 Vim 操作符后接无效动作时,会消费掉这个错误按键并回到普通模式。这样不会一直卡在等待状态。

数据流:它输入 d 进入待命,再输入 z;文本不变,光标不动,模式回 Normal,待命标志清除。

调用关系:这是 Vim 操作符状态机的错误路径测试。

调用图:外部调用 5 个(Char, new, assert!, assert_eq!, ta_with)。

tests::vim_e_lands_on_word_end_character2612–2625 ↗
fn vim_e_lands_on_word_end_character()

作用:检查 Vim 的 e 会把光标移动到当前词的最后一个字符上。随后按 x 应删除这个字符。

数据流:它在 abc 开头按 e,光标到 c;再按 x,文本变 ab,kill_buffer 保存 c。

调用关系:测试运行器用它验证 Vim 词尾移动和删除当前字符能连起来工作。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_e_advances_from_each_word_end2628–2640 ↗
fn vim_e_advances_from_each_word_end()

作用:检查连续按 e 时,光标会从一个词尾前进到下一个词尾。它用快照记录多个中间状态。

数据流:它准备 alpha beta gamma,从 alpha 末尾附近开始,连续三次发送 e;每次把文本和光标位置画成字符串;最终与保存的快照比较。

调用关系:这是 Vim 词导航的回归测试。它使用 insta 快照来发现行为变化。

调用图:外部调用 6 个(Char, new, new, format!, assert_snapshot!, ta_with)。

tests::vim_delete_to_word_end_advances_from_existing_word_end2643–2653 ↗
fn vim_delete_to_word_end_advances_from_existing_word_end()

作用:检查在已经接近词尾时执行 de,会继续删到后面词的词尾,而不是只删一个字符。这样符合 Vim 的移动语义。

数据流:它在 alpha 的末尾前执行 d e;结果删除 a beta,文本变 alph gamma,kill_buffer 保存被删内容。

调用关系:测试运行器用它验证 Vim 删除操作和 e 移动的组合规则。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_e_from_word_end_can_land_on_trailing_space2656–2664 ↗
fn vim_e_from_word_end_can_land_on_trailing_space()

作用:检查从词尾继续按 e 时,可以落到尾随空格上的特定位置。这个细节影响后续删除或移动命令。

数据流:它准备 alpha 后跟空格,从 alpha 末尾附近按 e;光标移动到预期的尾部空格位置。

调用关系:这是 Vim 词尾移动在空白结尾场景下的测试。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_e_advances_across_atomic_element_word_ends2667–2681 ↗
fn vim_e_advances_across_atomic_element_word_ends()

作用:确认 Vim 的 e 能跨过不可拆分元素,并把元素当作一个整体处理。这样附件占位符不会被光标切开。

数据流:它插入 alpha、一个元素和 gamma;第一次按 e 到元素起点,第二次按 e 到 gamma 的词尾附近;每步检查光标。

调用关系:测试运行器用它验证 Vim 词导航和特殊元素的配合。

调用图:调用 1 个内部函数(new);外部调用 3 个(Char, new, assert_eq!)。

tests::vim_dollar_lands_on_line_end_character2684–2697 ↗
fn vim_dollar_lands_on_line_end_character()

作用:检查 Vim 的 $ 会把光标放到当前行最后一个字符,而不是换行符上。随后 x 应删除这个行尾字符。

数据流:它在 abc 行中按 $,光标到 c;再按 x,文本变 ab 换行 123,kill_buffer 保存 c。

调用关系:这是 Vim 行尾导航和删除当前字符的组合测试。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::vim_linewise_yank_pastes_below_current_line2700–2713 ↗
fn vim_linewise_yank_pastes_below_current_line()

作用:检查 yy 复制整行后,p 会把这行粘贴到当前行下面。linewise 表示按整行处理。

数据流:它准备三行文本,在第一行执行 y y p;结果第一行被复制到下面,光标到新插入行,kill_buffer 记录整行并标记为 Linewise。

调用关系:测试运行器用它验证 Vim 整行复制和粘贴规则。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::delete_backward_word_and_kill_line_variants2716–2764 ↗
fn delete_backward_word_and_kill_line_variants()

作用:检查按词向后删除,以及删到行尾、删到行首这些常见编辑命令的各种边界。它覆盖行中、行尾、行首和跨换行的情况。

数据流:它多次创建不同文本,设置光标后调用 delete_backward_word、kill_to_end_of_line、kill_to_beginning_of_line;每次检查文本和光标是否正确。

调用关系:这是非 Vim 编辑命令的综合测试。它直接调用 TextArea 的编辑方法,不通过按键映射。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::kill_current_line_removes_current_line_linewise2767–2777 ↗
fn kill_current_line_removes_current_line_linewise()

作用:检查删除当前行时,会按整行方式删除,并把被删行放进 kill_buffer。这样后续粘贴能按行恢复。

数据流:它准备三行文本,光标在中间行;调用 kill_current_line;结果中间行消失,光标到合适位置,kill_buffer 保存 def 加换行并标记 Linewise。

调用关系:测试运行器用它验证整行删除的核心行为。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::kill_current_line_keeps_previous_newline_for_final_line2780–2790 ↗
fn kill_current_line_keeps_previous_newline_for_final_line()

作用:检查删除最后一行时,会保留前一行的换行结构,不把前一行也弄乱。末行没有尾随换行时尤其容易出错。

数据流:它准备 abc 换行 def,光标在 def;删除当前行后文本为 abc 加换行,kill_buffer 保存 def,光标在末尾换行后。

调用关系:这是 kill_current_line 的最后一行边界测试。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::kill_whole_line_keymap_dispatch_uses_linewise_kill2793–2809 ↗
fn kill_whole_line_keymap_dispatch_uses_linewise_kill()

作用:检查通过快捷键映射触发“删除整行”时,走的是整行删除规则。也就是说按键分发不能误走删到行首的规则。

数据流:它准备三行文本,修改运行时键位表,把 Ctrl+U 绑定到 kill_whole_line;发送按键后,确认中间行被整行删除并存入 linewise 缓冲。

调用关系:测试运行器用它验证 RuntimeKeymap 到 TextArea 编辑命令的分发。

调用图:调用 1 个内部函数(defaults);外部调用 5 个(Char, new, assert_eq!, ta_with, vec!)。

tests::delete_forward_word_variants2812–2848 ↗
fn delete_forward_word_variants()

作用:检查向前按词删除在不同位置的行为,包括词首、词中、结尾、换行前和光标越界。它保证删除范围符合用户直觉。

数据流:它用多组文本设置不同光标,调用 delete_forward_word;每组都检查删除后的文本和光标位置。

调用关系:这是前向按词删除的综合测试,直接调用 TextArea 方法。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::delete_forward_word_handles_atomic_elements2851–2882 ↗
fn delete_forward_word_handles_atomic_elements()

作用:确认向前按词删除遇到特殊元素时,会把元素当作整体删除。即使光标落在元素内部,也会回到元素起点并删整块。

数据流:它构造元素在开头、空格后、普通文字后的几种场景;调用 delete_forward_word;结果元素被整体移除,光标停在合理边界。

调用关系:测试运行器用它检查普通词删除逻辑和不可拆分元素的配合。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

tests::delete_backward_word_respects_word_separators2885–2907 ↗
fn delete_backward_word_respects_word_separators()

作用:检查向后按词删除会把斜杠等分隔符当成边界。这样编辑路径 path/to/file 时不会一次删太多。

数据流:它准备带斜杠和空格的文本,光标放末尾;反复调用 delete_backward_word;每次确认只删预期的词或分隔符片段。

调用关系:这是词边界规则的测试,重点是标点分隔符。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::delete_forward_word_respects_word_separators2910–2932 ↗
fn delete_forward_word_respects_word_separators()

作用:检查向前按词删除同样尊重斜杠等分隔符。光标在路径开头时,应一段一段删。

数据流:它准备 path/to/file、/ foo、 /foo 等文本;调用 delete_forward_word;结果只删除当前词或分隔符范围,光标保持合理。

调用关系:测试运行器用它验证前向删除和词分隔规则。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::yank_restores_last_kill2935–2965 ↗
fn yank_restores_last_kill()

作用:检查 yank 粘贴能恢复最近一次删除的内容。这里的 kill_buffer 就像剪贴板,保存刚删掉的文字。

数据流:它分别测试删到行尾、向后删词、删到行首;每次删除后调用 yank;文本应恢复,光标到插入内容末尾。

调用关系:这是删除和粘贴之间的配合测试。它直接调用编辑命令和 yank。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::kill_buffer_persists_across_set_text2968–2980 ↗
fn kill_buffer_persists_across_set_text()

作用:确认修改输入框当前文本不会清掉 kill_buffer。这样临时换内容后,用户仍能粘贴之前剪下的东西。

数据流:它先删除 restore me 到 kill_buffer,再把文本改成别的又清空;随后 yank;结果 restore me 被恢复出来。

调用关系:测试运行器用它保护剪贴缓冲区的生命周期规则。

调用图:外部调用 3 个(assert!, assert_eq!, ta_with)。

tests::cursor_left_and_right_handle_graphemes2983–3003 ↗
fn cursor_left_and_right_handle_graphemes()

作用:检查左右移动光标时,会按用户看到的完整字符移动,包括 emoji。不能停在 emoji 的内部字节位置。

数据流:它准备 ab,从末尾连续左移三次并记录位置,再右移三次;最终光标回到文本末尾,各中间位置单调变化。

调用关系:这是光标移动的 Unicode 安全测试。

调用图:外部调用 3 个(assert!, assert_eq!, ta_with)。

tests::control_b_and_f_move_cursor3006–3015 ↗
fn control_b_and_f_move_cursor()

作用:检查 Ctrl+F 和 Ctrl+B 能分别向右、向左移动光标。这是类似 Emacs 的常见快捷键。

数据流:它准备 abcd,光标在 1;发送 Ctrl+F 后光标到 2,再发送 Ctrl+B 后回到 1。

调用关系:测试运行器通过 input 验证快捷键到移动命令的映射。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::control_b_f_fallback_control_chars_move_cursor3018–3030 ↗
fn control_b_f_fallback_control_chars_move_cursor()

作用:检查有些终端直接发送控制字符时,输入框仍能识别 Ctrl+B 和 Ctrl+F。控制字符是早期终端用来表示快捷键的隐藏字符。

数据流:它发送 U+0002 代表 Ctrl+B,光标左移;再发送 U+0006 代表 Ctrl+F,光标右移。

调用关系:这是终端兼容性测试,确保没有显式 CONTROL 修饰键时也能正常移动。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::c0_line_feed_inserts_newline_through_insert_newline_keymap3033–3041 ↗
fn c0_line_feed_inserts_newline_through_insert_newline_keymap()

作用:检查 C0 控制字符里的换行符能按键位配置插入真正的新行。C0 控制字符是一些不可见的老式终端控制码。

数据流:它准备 ab,光标在中间;发送 U+000A;文本变成 a 换行 b,光标在新行后。

调用关系:测试运行器用它验证控制字符会经过插入换行的键位路径。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::c0_control_chars_respect_unbound_editor_movement3044–3056 ↗
fn c0_control_chars_respect_unbound_editor_movement()

作用:检查控制字符快捷键如果在键位表里被解绑,就不会偷偷执行默认移动。用户改了键位,就应该尊重。

数据流:它清空 move_up 绑定,再发送代表 Ctrl+P 的控制字符;光标保持不变。

调用关系:这是 RuntimeKeymap 自定义行为测试,通过 input_with_keymap 走指定键位表。

调用图:调用 1 个内部函数(defaults);外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::c0_control_chars_respect_remapped_editor_movement3059–3072 ↗
fn c0_control_chars_respect_remapped_editor_movement()

作用:检查控制字符能跟随用户重映射后的键位执行。比如把 Ctrl+P 改成向下移动,就应该真的向下。

数据流:它改键位表,把 move_down 绑定到 Ctrl+P;发送对应控制字符;光标从第一行移动到下一行。

调用关系:测试运行器用它验证控制字符解析和运行时键位映射能配合。

调用图:调用 1 个内部函数(defaults);外部调用 5 个(Char, new, assert_eq!, ta_with, vec!)。

tests::delete_backward_word_alt_keys3075–3092 ↗
fn delete_backward_word_alt_keys()

作用:检查两种 Alt 组合键都能向后删除一个词。Alt+Ctrl+h 和 Alt+Backspace 都是常见终端里“删前一个词”的表示。

数据流:它分别准备 hello world,把光标放末尾;发送两个不同快捷键;结果都只剩 hello 加空格,光标在空格后。

调用关系:这是快捷键兼容性测试,通过 TextArea::input 走真实按键分发。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::shift_backspace_and_shift_delete_keep_grapheme_delete_behavior3095–3109 ↗
fn shift_backspace_and_shift_delete_keep_grapheme_delete_behavior()

作用:检查 Shift+Backspace 和 Shift+Delete 仍然按普通字符删除,不会因为多了 Shift 就改变语义。这里也保护按完整字符删除的行为。

数据流:它分别在 abc 中用 Shift+Backspace 和 Shift+Delete;结果都删除 b,光标位置正确。

调用关系:测试运行器用它验证删除键修饰符处理。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::control_backspace_variants_delete_backward_word3112–3124 ↗
fn control_backspace_variants_delete_backward_word()

作用:确认 Ctrl+Backspace 以及 Ctrl+Shift+Backspace 都是向后删一个词。不同系统可能发送不同修饰组合。

数据流:它循环测试两个修饰符组合,光标在 hello world 末尾;按键后 world 被删,光标到 hello 后。

调用关系:这是后向按词删除快捷键的兼容测试。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::control_delete_variants_delete_forward_word3127–3139 ↗
fn control_delete_variants_delete_forward_word()

作用:确认 Ctrl+Delete 和 Ctrl+Shift+Delete 都是向前删一个词。这样键盘行为和多数编辑器一致。

数据流:它循环测试两个修饰符组合,光标在 hello world 开头;按键后 hello 被删,光标留在开头。

调用关系:这是前向按词删除快捷键的兼容测试。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::delete_backward_word_handles_narrow_no_break_space3142–3148 ↗
fn delete_backward_word_handles_narrow_no_break_space()

作用:检查窄不换行空格这种特殊空白字符不会破坏按词删除。它常出现在时间或排版文本里。

数据流:它准备 32 后跟窄不换行空格和 AM;按 Alt+Backspace 后只删除 AM,保留前面的数字和特殊空格。

调用关系:这是 Unicode 空白字符的词删除测试。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::delete_forward_word_with_without_alt_modifier3151–3163 ↗
fn delete_forward_word_with_without_alt_modifier()

作用:检查 Delete 键带 Alt 时按词删除,不带 Alt 时只删一个字符。修饰键决定删除粒度。

数据流:第一段在 hello world 开头按 Alt+Delete,删除 hello;第二段在 hello 开头按普通 Delete,只删除 h。

调用关系:测试运行器用它验证 Delete 键的修饰符分发。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::delete_forward_word_alt_d3166–3172 ↗
fn delete_forward_word_alt_d()

作用:确认 Alt+D 能向前删除一个词,这是很多命令行编辑器里的常见快捷键。

数据流:它准备 hello world,光标在 world 前;发送 Alt+D;world 被删,光标保持在原位置。

调用关系:这是 Alt+D 快捷键到 delete_forward_word 的映射测试。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::control_h_backspace3175–3194 ↗
fn control_h_backspace()

作用:检查 Ctrl+H 被当作退格键处理。很多终端会把 Backspace 发成 Ctrl+H。

数据流:它在 12345 中间、开头和结尾分别发送 Ctrl+H;中间和结尾会删前一个字符,开头不变。

调用关系:这是终端兼容性测试,确保不同退格表示都能正常工作。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::altgr_ctrl_alt_char_inserts_literal3198–3206 ↗
fn altgr_ctrl_alt_char_inserts_literal()

作用:检查 Ctrl+Alt+字符在某些情况下会被当作 AltGr 输入的普通字符,而不是快捷键。AltGr 是一些键盘布局用来输入特殊字符的键。

数据流:它向空文本发送 Ctrl+Alt+c;结果插入字母 c,光标前进到 1。

调用关系:测试运行器用它避免误把 AltGr 输入吞掉。

调用图:外部调用 4 个(Char, new, assert_eq!, ta_with)。

tests::cursor_vertical_movement_across_lines_and_bounds3209–3238 ↗
fn cursor_vertical_movement_across_lines_and_bounds()

作用:检查上下移动光标时,会尽量保持列位置,同时在短行和文本边界处安全收缩。用户按上下键时期待光标垂直移动而不是乱跳。

数据流:它准备长短不一的三行文本,把光标放第二行;向上、再向上、向下多次移动;每一步检查光标是否在合理行和边界内。

调用关系:这是基础竖向光标移动测试,直接调用 move_cursor_up 和 move_cursor_down。

调用图:外部调用 3 个(assert!, assert_eq!, ta_with)。

tests::home_end_and_emacs_style_home_end3241–3263 ↗
fn home_end_and_emacs_style_home_end()

作用:检查 Home/End 类移动,以及类似 Ctrl+A/Ctrl+E 的行首行尾跳转。某些模式下在行首再按会跳到上一行行首,在行尾再按会跳到下一行行尾。

数据流:它准备三行文本,把光标放第二行中间;调用移动到行首、再按行首上跳、移动到行尾、再按行尾下跳;断言检查位置。

调用关系:测试运行器用它验证行内导航和 Emacs 风格边界行为。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::end_of_line_or_down_at_end_of_text3266–3279 ↗
fn end_of_line_or_down_at_end_of_text()

作用:检查在文本绝对末尾执行“到行尾或下一行”不会崩溃,也不会越界。边界位置必须安全。

数据流:它先把光标放文本末尾,调用移动到行尾并允许下跳,结果不变;再放第一行行尾,调用后跳到下一行末尾。

调用关系:这是 move_cursor_to_end_of_line 的末尾边界测试。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::word_navigation_helpers3282–3298 ↗
fn word_navigation_helpers()

作用:检查查找上一个词开头和下一个词结尾的辅助函数。它们是按词移动和按词删除的基础零件。

数据流:它准备含多个词和空格的文本,设置不同光标;调用 beginning_of_previous_word 和 end_of_next_word;结果应返回预期索引。

调用关系:测试运行器用它直接验证词导航底层函数,不经过按键输入。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::word_navigation_cjk_each_char_is_boundary3301–3316 ↗
fn word_navigation_cjk_each_char_is_boundary()

作用:检查中文等 CJK 字符向后按词移动时,每个字都可以作为边界。CJK 指中文、日文、韩文这类文字。

数据流:它准备“你好世界”,从末尾逐步调用 beginning_of_previous_word;每次都退到前一个汉字的起点。

调用关系:这是 Unicode 词边界测试,重点是 CJK 文字。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::word_navigation_cjk_forward3319–3334 ↗
fn word_navigation_cjk_forward()

作用:检查 CJK 字符向前按词移动时,也是一字一跳。这样中文输入的光标移动更符合直觉。

数据流:它从“你好世界”的各个字节边界调用 end_of_next_word;结果依次到下一个汉字末尾。

调用关系:测试运行器用它验证前向词导航的 CJK 规则。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::word_navigation_mixed_ascii_cjk3337–3355 ↗
fn word_navigation_mixed_ascii_cjk()

作用:检查英文和中文混在一起时,词边界能正确分开。hello 应算一个英文词,每个中文字符也有自己的边界。

数据流:它准备 hello你好,分别测试前进和后退词边界;结果在英文词尾和每个中文字符边界处停下。

调用关系:这是混合语言文本的词导航测试。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::word_navigation_preserves_separator_breaks_within_unicode_segments3358–3372 ↗
fn word_navigation_preserves_separator_breaks_within_unicode_segments()

作用:检查撇号、小数点、句点等分隔符会被保留为词边界。比如 can't、32.3、foo.bar 不应被简单当成一个整体。

数据流:它准备包含标点的文本,设置光标在不同位置;调用 beginning_of_previous_word;结果会落到标点附近的正确边界。

调用关系:测试运行器用它保护更细的词分隔规则。

调用图:外部调用 2 个(assert_eq!, ta_with)。

tests::wrapping_and_cursor_positions3375–3400 ↗
fn wrapping_and_cursor_positions()

作用:检查文本自动换行后,高度计算、光标坐标和滚动状态是否正确。终端输入框宽度有限,长文本必须拆成多行显示。

数据流:它准备长句并设置窄区域;检查 desired_height 至少有多行;把光标放在换行后的单词里,确认坐标;再用小高度渲染,确认滚动让光标可见。

调用关系:这是渲染和光标定位的综合测试,会调用 render_ref 更新 TextAreaState。

调用图:外部调用 7 个(empty, new, assert!, assert_eq!, render_ref, default, ta_with)。

tests::render_highlights_apply_style_without_mutating_text3403–3437 ↗
fn render_highlights_apply_style_without_mutating_text()

作用:检查渲染高亮只改变屏幕样式,不改变真实文本。搜索结果高亮应该像荧光笔标记,不应该改字本身。

数据流:它准备 hello world,渲染 world 范围为反色;然后确认 TextArea 的文本仍不变,同时缓冲区中高亮位置带有反色样式。

调用关系:测试运行器用它验证 render_ref_styled_with_highlights 这条渲染路径。

调用图:外部调用 7 个(empty, new, default, assert!, assert_eq!, default, ta_with)。

tests::cursor_pos_with_state_basic_and_scroll_behaviors3440–3478 ↗
fn cursor_pos_with_state_basic_and_scroll_behaviors()

作用:检查带滚动状态的光标坐标计算。内容能完全显示时滚动应被忽略,内容过长时光标要被夹在可见区域内。

数据流:它分别构造内容适配、光标在窗口下方、光标在窗口上方三种情况;调用 cursor_pos_with_state;结果 y 坐标被正确映射到可见行。

调用关系:这是 TextAreaState 滚动逻辑的测试,直接调用坐标计算函数。

调用图:外部调用 3 个(new, assert_eq!, ta_with)。

tests::wrapped_navigation_across_visual_lines3481–3516 ↗
fn wrapped_navigation_across_visual_lines()

作用:检查自动换行产生的“视觉行”之间,上下移动光标是否正确。视觉行是屏幕上看到的一行,不一定是真实换行。

数据流:它用宽度 4 把 abcdefghij 拆成三行显示;测试从第一视觉行向下、从第二行向上再向下、到短尾行时收缩到末尾。

调用关系:测试运行器用它验证移动逻辑基于视觉行,而不只是真实换行符。

调用图:外部调用 4 个(new, assert_eq!, default, ta_with)。

tests::cursor_pos_with_state_after_movements3519–3560 ↗
fn cursor_pos_with_state_after_movements()

作用:检查光标移动和渲染滚动状态连续配合时,坐标始终正确。用户上下移动时,窗口应跟着滚动让光标看得见。

数据流:它用宽度 4 和高度 2 的区域渲染文本;多次移动光标并调用 render_ref 更新状态;每次用 cursor_pos_with_state 确认屏幕坐标。

调用关系:这是渲染、滚动状态和光标移动的联动测试。render_ref 会调整 state,随后坐标函数读取它。

调用图:外部调用 6 个(empty, new, assert_eq!, render_ref, default, ta_with)。

tests::wrapped_navigation_with_newlines_and_spaces3563–3585 ↗
fn wrapped_navigation_with_newlines_and_spaces()

作用:检查自动换行、空格和真实换行混在一起时,上下移动仍能保持合理列位置。这里既有视觉行边界,也有真实行边界。

数据流:它准备带空格和换行的文本,用窄宽度触发换行;光标在 word2 上,向上回到第一视觉行,向下回到 word2,再向下跨到 word3。

调用关系:测试运行器用它覆盖更接近真实输入的换行导航场景。

调用图:外部调用 3 个(assert!, assert_eq!, ta_with)。

tests::wrapped_navigation_with_wide_graphemes3588–3605 ↗
fn wrapped_navigation_with_wide_graphemes()

作用:检查宽字符比如 emoji 自动换行后,上下移动不会落进字符中间。宽字符在终端上可能占两个格子。

数据流:它准备四个点赞 emoji,用很窄的宽度强制换行;光标在第二个 emoji 后,向下移动到下一视觉行,再向上应回到原位。

调用关系:这是自动换行导航和 Unicode 宽字符边界的测试。

调用图:外部调用 3 个(assert!, assert_eq!, ta_with)。

tests::fuzz_textarea_randomized3608–3836 ↗
fn fuzz_textarea_randomized()

作用:这个函数是文本框的随机压力测试。它模拟用户用各种不可预测的方式编辑文本,确认文本框在复杂字符、光标移动、删除替换、内嵌元素和终端渲染时都不会出错。

数据流:进去的是当天日期生成的随机种子,以及测试里临时造出的随机文字、随机光标位置、随机窗口宽高和随机操作。函数先创建一个新的 TextArea 和它的显示状态,然后循环很多轮:随机改文字、移动光标、插入特殊元素、尝试在元素中间操作、渲染到 Buffer(终端界面的内存画布)里。出来的不是业务结果,而是一组断言检查:如果光标越界、元素内容被意外改坏、光标停进元素内部、渲染崩溃或滚动状态不合理,测试就会失败;如果都没问题,就说明这些随机场景下文本框行为可靠。

调用关系:它位于测试模块里,平时由测试框架运行,不是用户直接调用的功能。它会大量调用 TextArea 的核心能力,比如 new 创建文本框、set_text_clearing_elements 设置文本、insert_str 插入文字、replace_range 替换范围、delete_backward/delete_forward 删除、各种 move_cursor 移动光标、insert_element 插入整体元素、desired_height 计算换行后的高度,以及 cursor_pos 和 cursor_pos_with_state 计算屏幕上的光标位置。最后它还把文本框交给 ratatui 的渲染接口画到缓冲区,用这种方式同时检查编辑逻辑和显示逻辑是否能配合工作。

调用图:调用 1 个内部函数(new);外部调用 15 个(empty, new, new, new, assert!, assert_eq!, hours, now, format!, seed_from_u64 (+5 more))。

tui/src/bottom_pane/chat_composer/draft_state.rs源码 ↗
data_modelmain loop / 用户编辑聊天草稿期间

这个文件像是聊天输入框旁边的一张“草稿记录表”。用户在底部输入消息时,程序不只要记住输入了哪些字,还要记住光标在哪、当前是不是 Bash 模式、输入框能不能用、刚粘贴的大段内容要不要特殊处理,以及用户提到的文件或对象对应到哪里。DraftState 就是这张表。它里面的 textarea 保存文本框本身,textarea_state 保存光标和选择等可变状态,pending_pastes 暂存还没处理完的粘贴内容,mention_bindings 记录输入里的“提及”和真实路径之间的关系。ComposerMentionBinding 则是一条具体提及记录,包含符号、显示文字和路径。没有这个文件,输入框的数据会散落在控制代码里,改起来容易乱,也容易在提交、粘贴、禁用输入时丢状态。

函数细节1
DraftState::new25–38 ↗
fn new() -> Self

作用:创建一份全新的空草稿状态,给聊天输入框从零开始使用。它会把文本框、光标状态、粘贴记录、提及记录等都设置成安全的初始值。

数据流:进去不需要任何外部输入 → 它新建一个空的 TextArea,准备默认的 TextAreaState,把 Bash 模式关掉,把待处理粘贴和提及列表清空,把输入设为可用,并建立默认的粘贴突发检测器 → 出来是一份完整可用的 DraftState,之后输入框可以直接拿它来记录用户正在编辑的内容。

调用关系:它通常由更上层的 new_with_config 在创建聊天输入组件时调用。它自己会把初始化工作交给一些更小的零件,比如文本框的 new、默认状态的 default、列表和表的创建函数;这样上层不需要知道每个字段该怎么从零摆好。

调用图:调用 1 个内部函数(new);被 1 处调用(new_with_config);外部调用 5 个(new, new, new, default, default)。

tui/src/bottom_pane/chat_composer/popup_state.rs源码 ↗
data_modelmain loop

在聊天输入框里,用户可能输入不同内容触发不同弹窗:比如输入命令时弹出命令列表,搜索文件时弹出文件选择,提到某个对象时弹出提及建议。这个文件就像一个前台接待员,只记录“现在谁正在窗口服务”,并记住一些刚被用户关掉的提示,避免用户刚关掉它又马上被自动弹出来。核心结构是 PopupState,它保存当前激活的弹窗 active,以及文件搜索、提及相关的查询和已关闭标记。ActivePopup 是一个枚举,也就是“几种可能状态中只能选一种”的类型:没有弹窗、命令弹窗、文件弹窗、技能弹窗、提及弹窗。这样写的好处是很直接:界面其他地方只要问它“现在有没有弹窗”,就能决定输入框、快捷键或其他界面行为该怎么配合。

函数细节1
PopupState::active18–20 ↗
fn active(&self) -> bool

作用:这个函数用来回答一个简单问题:现在有没有弹窗正在显示。外部界面逻辑会用它来决定是否要把输入、快捷键或其他行为让给弹窗。

数据流:它读取 PopupState 里的 active 字段,看当前状态是不是 ActivePopup::None。若不是 None,就返回 true,表示有弹窗;如果是 None,就返回 false,表示没有弹窗。它不修改任何数据,只做一次状态判断。

调用关系:当界面需要知道弹窗是否存在时,会通过 popup_active 或 set_input_enabled 间接用到它。它内部只是用 matches! 这个模式匹配工具做判断,相当于看一眼当前登记的弹窗类型,然后把“有”或“没有”的答案交回给调用者。

调用图:被 2 处调用(popup_active, set_input_enabled);外部调用 1 个(matches!)。

tui/src/bottom_pane/chat_composer/attachment_state.rs源码 ↗
domain_logicrequest handling

聊天框里不能直接塞进图片文件本身,所以系统会放一个像“图片 1”这样的占位符,真正的图片路径或远程链接则存在这里。这个文件就像一张小清单:本地图片有哪些、远程图片 URL 有哪些、键盘当前选中了哪一条远程图片。它还会在远程图片增加或删除后,重新给本地图片编号,保证界面上看到的编号和后台要发送的图片顺序一致。用户按上下键选择远程图片、按删除键移除远程图片,或者手动删掉输入框里的本地图片占位符时,这里都会同步更新清单。最重要的是,它防止“界面显示一回事,实际发送另一回事”这种很难发现的错误。

函数细节18
AttachmentState::is_empty34–36 ↗
fn is_empty(&self) -> bool

作用:判断当前聊天输入框有没有任何图片附件。外层代码可以用它来决定要不要显示附件区域,或发送时是否需要附带图片。

数据流:进去的是当前附件状态 → 它检查本地图片列表和远程图片链接列表是不是都为空 → 出来一个真假值,表示是否完全没有图片,不会改动任何数据。

调用关系:这是一个简单的状态查询函数,通常会被界面或提交流程在需要判断“有没有附件”时使用;它不把工作交给别的函数。

AttachmentState::local_image_paths38–43 ↗
fn local_image_paths(&self) -> Vec<PathBuf>

作用:取出所有本地图片的文件路径。有人只关心图片文件在哪里、不关心占位符文字时会用它。

数据流:进去的是当前保存的本地图片记录 → 它逐个拿出里面的路径并复制一份 → 出来一个路径列表,原来的附件状态不变。

调用关系:这是给外部读取本地图片路径的出口之一;它只读自己的清单,不调用其他内部步骤。

AttachmentState::local_images45–53 ↗
fn local_images(&self) -> Vec<LocalImageAttachment>

作用:取出本地图片的完整发送信息,包括占位符和文件路径。发送或预览时需要知道“哪个占位符对应哪个文件”,就会用到它。

数据流:进去的是当前本地图片记录 → 它把内部记录转换成聊天面板通用的 LocalImageAttachment 格式 → 出来一个新的附件列表,不改变内部状态。

调用关系:这是把内部记账格式交给外部使用的函数;它不处理键盘、不改输入框,只做安全的复制和格式转换。

AttachmentState::set_remote_image_urls55–59 ↗
fn set_remote_image_urls(&mut self, urls: Vec<String>, textarea: &mut TextArea)

作用:一次性设置远程图片链接列表。远程图片数量变了,本地图片的编号也要跟着往后排,所以它会顺手重新编号。

数据流:进去的是新的远程图片 URL 列表和输入框对象 → 它替换旧的远程 URL,清掉当前远程图片选择状态,再调用重新编号逻辑 → 出来时远程图片变成新列表,输入框里的本地图片占位符也可能被改名。

调用关系:当外部拿到一组新的远程图片链接时会走这里;它把编号修正的活交给 AttachmentState::relabel_local_images,避免远程图片和本地图片编号撞车。

调用图:调用 1 个内部函数(relabel_local_images)。

AttachmentState::remote_image_urls61–63 ↗
fn remote_image_urls(&self) -> Vec<String>

作用:取出当前所有远程图片链接。外层代码要发送、显示或检查远程图片时会用它。

数据流:进去的是当前附件状态 → 它复制远程图片 URL 列表 → 出来一个独立的新列表,内部保存的 URL 不变。

调用关系:这是远程图片清单的只读出口;它不参与选择、删除或重新编号。

AttachmentState::take_remote_image_urls65–70 ↗
fn take_remote_image_urls(&mut self, textarea: &mut TextArea) -> Vec<String>

作用:把远程图片链接一次性取走,并把这里的远程图片清空。适合在提交消息时使用,因为提交后这些远程图片不该继续留在输入框状态里。

数据流:进去的是当前状态和输入框对象 → 它用 take 把远程 URL 列表搬走,让内部列表变空,清掉选择状态,再重新给本地图片编号 → 出来的是原来的远程 URL 列表,状态里不再保存这些远程图。

调用关系:它通常出现在发送流程中;取走数据后会调用 AttachmentState::relabel_local_images,因为远程图片没了,本地图片的编号要从前面补上。

调用图:调用 1 个内部函数(relabel_local_images);外部调用 1 个(take)。

AttachmentState::clear_remote_image_urls72–75 ↗
fn clear_remote_image_urls(&mut self)

作用:清空所有远程图片链接,并取消当前选中项。它用于放弃或重置远程图片附件。

数据流:进去的是当前附件状态 → 它清掉远程 URL 列表,并把选中的远程图片位置设为空 → 没有返回值,本地图片不重新编号。

调用关系:这是一个直接清理远程图片的工具函数;和 take_remote_image_urls 不同,它不返回被清掉的链接,也不触碰输入框。

AttachmentState::reset_local_images77–94 ↗
fn reset_local_images(
        &mut self,
        local_image_paths: Vec<PathBuf>,
        textarea: &mut TextArea,
    )

作用:用一批新的本地图片路径重建本地图片清单。它常用于从外部状态恢复输入框,或者重新载入附件列表。

数据流:进去的是新的本地图片路径列表和输入框对象 → 它先清空旧本地图片,再按当前远程图片数量给这些本地图生成占位符编号,清掉远程选择,最后修正输入框里的占位符 → 出来时本地图片清单完全换成新的一批。

调用关系:这是本地图片状态的大重置入口;它最后调用 AttachmentState::relabel_local_images,确保输入框文字和内部新编号一致。

调用图:调用 1 个内部函数(relabel_local_images)。

AttachmentState::attach_image96–101 ↗
fn attach_image(&mut self, textarea: &mut TextArea, path: PathBuf)

作用:把一张新的本地图片加到聊天输入框里。它会生成一个“图片编号”占位符,并把这个占位符插进输入框。

数据流:进去的是输入框对象和图片文件路径 → 它根据远程图片数和已有本地图片数算出下一个编号,用 local_image_label_text 生成占位符,再让输入框 insert_element 插入这个占位符,最后把路径和占位符记到账本里 → 出来时输入框多了一个图片标记,状态里也多了一张本地图片。

调用关系:当用户添加本地图片时会走这里;它依赖占位符生成函数和输入框的插入能力,让界面显示和后台记录同时更新。

调用图:调用 2 个内部函数(local_image_label_text, insert_element)。

AttachmentState::prune_local_images_for_submission103–118 ↗
fn prune_local_images_for_submission(
        &mut self,
        text: &str,
        text_elements: &[TextElement],
    )

作用:发送前删掉那些已经不在输入文字里的本地图片记录。这样用户手动删掉占位符后,系统不会偷偷把那张图片也发出去。

数据流:进去的是当前文本和解析出来的文本元素 → 它从文本元素里找出还存在的图片占位符,做成一张快速查找表,然后只保留占位符仍在文本里的本地图片 → 没有返回值,但本地图片清单可能变短。

调用关系:它通常在提交消息前运行;它不改输入框文字,只根据文本元素这份事实清理内部附件清单。

调用图:外部调用 1 个(iter)。

AttachmentState::take_recent_submission_images121–126 ↗
fn take_recent_submission_images(&mut self) -> Vec<PathBuf>

作用:测试中使用:取走最近要提交的本地图片路径,并清空本地图片清单。它帮助测试确认哪些图片会被发送。

数据流:进去的是当前本地图片状态 → 它用 take 把整个本地图片列表搬空,再只取出每张图片的路径 → 出来一个路径列表,内部本地图片列表变为空。

调用关系:这个函数只在测试编译时存在;它服务于测试断言,不参与正式界面的普通运行。

调用图:外部调用 1 个(take)。

AttachmentState::take_recent_submission_images_with_placeholders128–138 ↗
fn take_recent_submission_images_with_placeholders(
        &mut self,
    ) -> Vec<LocalImageAttachment>

作用:取走最近要提交的本地图片,并保留每张图对应的占位符。发送流程需要知道“文本里的哪个标记对应哪个文件”时会用它。

数据流:进去的是当前本地图片状态 → 它用 take 把本地图片列表整体搬走并清空内部状态,再转换成 LocalImageAttachment 列表 → 出来的是带占位符和路径的附件列表,状态里不再保留这些本地图片。

调用关系:它适合提交消息时调用;和只取路径的测试函数相比,它保留了占位符信息,方便发送端把文本和图片正确配对。

调用图:外部调用 1 个(take)。

AttachmentState::remote_image_lines140–153 ↗
fn remote_image_lines(&self) -> Vec<Line<'static>>

作用:把远程图片列表变成可以显示在终端界面上的一行行文字。当前选中的那一行会用反色高亮,用户就知道自己选中了哪张。

数据流:进去的是远程图片 URL 列表和当前选中下标 → 它按顺序生成“图片 1、图片 2”这类标签,并给选中的标签加上高亮样式 → 出来是一组界面可直接渲染的 Line,不改变状态。

调用关系:这是显示层会调用的函数;它只把状态翻译成界面文字,不处理按键,也不删除图片。

AttachmentState::clear_remote_image_selection155–157 ↗
fn clear_remote_image_selection(&mut self)

作用:取消远程图片的当前选中状态。用户离开选择区域,或选中的图片被删完时,需要把这个状态清掉。

数据流:进去的是当前附件状态 → 它把 selected_remote_image_index 设为空 → 没有返回值,只改变选中状态。

调用关系:AttachmentState::handle_remote_image_selection_key 在用户按方向键离开列表时会用它,AttachmentState::remove_selected_remote_image 在删除后没有合适选中项时也会用它。

调用图:被 2 处调用(handle_remote_image_selection_key, remove_selected_remote_image)。

AttachmentState::handle_remote_image_selection_key159–205 ↗
fn handle_remote_image_selection_key(
        &mut self,
        key_event: &KeyEvent,
        textarea: &mut TextArea,
    ) -> Option<(InputResult, bool)>

作用:处理远程图片列表上的键盘操作。用户可以用上箭头进入或上移选择,用下箭头下移或退出选择,用删除键移除选中的远程图片。

数据流:进去的是一次键盘事件和输入框对象 → 它先排除不该处理的情况,比如没有远程图片、带修饰键、不是按下事件;然后根据按键更新选中下标,必要时查询输入框 cursor 位置,删除时调用删除函数 → 出来可能是一个输入处理结果和“事件已被处理”的标记,也可能返回空表示这次按键不归它管。

调用关系:这是远程图片键盘交互的入口;它在需要取消选择时调用 AttachmentState::clear_remote_image_selection,在需要真正删除图片时交给 AttachmentState::remove_selected_remote_image。

调用图:调用 3 个内部函数(clear_remote_image_selection, remove_selected_remote_image, cursor)。

AttachmentState::remove_deleted_local_placeholders207–223 ↗
fn remove_deleted_local_placeholders(
        &mut self,
        removed_payloads: &[String],
        textarea: &mut TextArea,
    ) -> bool

作用:当输入框里有图片占位符被删掉时,同步删掉对应的本地图片记录。否则文字里没有图片标记了,后台却还可能发送图片。

数据流:进去的是被删除的占位符文本列表和输入框对象 → 它从本地图片清单里移除占位符匹配的图片,判断长度有没有变化;如果确实删了图片,就重新编号剩下的本地图片 → 出来一个真假值,表示是否真的移除了本地图片,同时状态和输入框占位符可能被更新。

调用关系:它会在输入框报告某些元素被删除后使用;如果清单发生变化,它把编号整理工作交给 AttachmentState::relabel_local_images。

调用图:调用 1 个内部函数(relabel_local_images)。

AttachmentState::relabel_local_images225–235 ↗
fn relabel_local_images(&mut self, textarea: &mut TextArea)

作用:重新给本地图片占位符编号,并把输入框里的旧占位符也一起改掉。它是防止图片编号混乱的核心修补工。

数据流:进去的是当前远程图片数量、本地图片列表和输入框对象 → 它逐个计算本地图片应该有的新编号;如果旧占位符不对,就替换内部记录,并让输入框 replace_element_payload 把文本里的旧标记换成新标记 → 没有返回值,但本地图片占位符和输入框内容可能被同步改名。

调用关系:很多会改变图片数量或顺序的函数都会调用它,包括 AttachmentState::set_remote_image_urls、AttachmentState::take_remote_image_urls、AttachmentState::reset_local_images、AttachmentState::remove_deleted_local_placeholders 和 AttachmentState::remove_selected_remote_image。

调用图:调用 2 个内部函数(local_image_label_text, replace_element_payload);被 5 处调用(remove_deleted_local_placeholders, remove_selected_remote_image, reset_local_images, set_remote_image_urls, take_remote_image_urls);外部调用 1 个(replace)。

AttachmentState::remove_selected_remote_image237–250 ↗
fn remove_selected_remote_image(&mut self, selected_index: usize, textarea: &mut TextArea)

作用:删除当前选中的某一条远程图片链接,并把选择位置挪到合理的位置。删除远程图片后,本地图片编号也要重新排。

数据流:进去的是要删除的远程图片下标和输入框对象 → 如果下标越界,它只清掉选择;如果有效,它从远程 URL 列表删除那一项,再把选中位置移动到下一项或最后一项,列表空了就取消选择,最后重新编号本地图片 → 没有返回值,但远程图片列表、选中状态和本地图片占位符可能都会变化。

调用关系:它由 AttachmentState::handle_remote_image_selection_key 在用户按 Delete 或 Backspace 时调用;内部会按情况调用 AttachmentState::clear_remote_image_selection,并在删除成功后调用 AttachmentState::relabel_local_images。

调用图:调用 2 个内部函数(clear_remote_image_selection, relabel_local_images);被 1 处调用(handle_remote_image_selection_key)。

tui/src/bottom_pane/chat_composer_history.rs源码 ↗
domain_logicrequest handling / composer input handling

聊天输入框的历史记录不是简单的字符串列表。新提交的内容可能带图片、粘贴的大段内容、@ 提及等草稿信息;旧历史则可能还没读出来,需要异步去查。这个文件就是这套“回忆系统”的小状态机。它记录当前浏览到哪一条、哪些旧记录已经取回、搜索时见过哪些重复文本。按 Up/Down 时,它先从本地记录里拿;如果要看更早的持久化记录,就发一个查询事件,等回复回来再补上。Ctrl+R 搜索单独处理,因为搜索要跳过重复结果、支持继续向旧或向新找,并且到边界时不能偷偷移动光标。可以把它想成输入框旁边的“历史档案管理员”:本地抽屉马上拿,远程档案先登记请求,回来后再决定是否展示。

函数细节47
HistoryEntry::new49–51 ↗
fn new(text: String) -> Self

作用:创建一条只有文字的历史记录,适合从旧历史里恢复普通输入。它默认会尝试恢复历史里保存的提及信息。

数据流:输入一段历史文字 → 交给 HistoryEntry::new_with_at_mentions,并打开 @ 提及恢复开关 → 输出一个 HistoryEntry,附件、图片、粘贴内容等字段为空。

调用关系:这是创建历史条目的最常用入口;导航向下清空输入框、测试用例、持久化回复转换时都会间接或直接用它。

调用图:外部调用 1 个(new_with_at_mentions)。

HistoryEntry::new_with_at_mentions53–71 ↗
fn new_with_at_mentions(text: String, at_mentions_enabled: bool) -> Self

作用:创建文字历史记录,并按开关决定是否把历史里的 @ 提及恢复成可识别的提及绑定。这个开关用于兼容不同版本的提及写法。

数据流:输入原始文字和是否启用 @ 提及 → 调用 decode_history_mentions_with_at_mentions 解码历史里的提及标记 → 输出带纯文本和 mention_bindings 的 HistoryEntry,其他草稿附件字段为空。

调用关系:HistoryEntry::new 会调用它;ChatComposerHistory::on_entry_response 收到旧历史文字后也用它把字符串变回可放进输入框的草稿。

调用图:调用 1 个内部函数(decode_history_mentions_with_at_mentions);外部调用 1 个(new)。

HistoryEntry::with_pending74–88 ↗
fn with_pending(
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        pending_pastes: Vec<(String, String)>,
    ) -> Self

作用:测试里用来造一条带待恢复粘贴内容和本地图片的历史记录。它帮助验证复杂草稿不会在历史回放时丢信息。

数据流:输入文字、文本元素、本地图片路径、待粘贴内容 → 直接装进 HistoryEntry → 输出一条没有远程图片和提及绑定的测试记录。

调用关系:这是测试辅助函数,不参与正常运行;它让测试可以绕过真实输入框,直接准备带附件的历史条目。

调用图:外部调用 1 个(new)。

HistoryEntry::with_pending_and_remote91–106 ↗
fn with_pending_and_remote(
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        pending_pastes: Vec<(String, String)>,
        remote_image_

作用:测试里用来造一条同时带本地图片、远程图片和待粘贴内容的历史记录。它覆盖比 with_pending 更完整的草稿场景。

数据流:输入文字、文本元素、本地图片路径、待粘贴内容、远程图片网址 → 填入 HistoryEntry → 输出一条完整的测试历史记录。

调用关系:这是测试专用构造器;正常代码不会调用它,但测试复杂附件恢复时会需要这种假数据。

调用图:外部调用 1 个(new)。

ChatComposerHistory::new229–243 ↗
fn new() -> Self

作用:创建一个空的输入框历史管理器。刚创建时还不知道旧历史文件的信息,但已经可以记录本次会话的新输入。

数据流:不需要输入 → 初始化线程编号、旧历史数量、缓存、光标、搜索状态等字段为空或零 → 输出一个可开始使用的 ChatComposerHistory。

调用关系:输入框初始化和大量测试都会先调用它;后续 set_metadata 会补上持久化历史的信息。

调用图:被 12 处调用(new_with_config, duplicate_submissions_are_not_recorded, navigation_with_async_fetch, persistent_restore_gates_at_mentions, repeated_boundary_search_does_not_refetch_persistent_history, reset_navigation_resets_cursor, search_fetches_persistent_history_until_match, search_is_case_insensitive_and_empty_query_finds_latest, search_matches_local_history_and_stops_at_boundaries, search_skips_duplicate_local_matches (+2 more));外部调用 2 个(new, new)。

ChatComposerHistory::set_at_mention_restore_enabled245–254 ↗
fn set_at_mention_restore_enabled(&mut self, enabled: bool)

作用:切换恢复历史时是否把 @ 提及还原出来。切换后会清掉旧缓存,避免同一条历史按旧规则和新规则混着用。

数据流:输入一个布尔值 enabled → 如果值没变就不动;如果变了,就保存新设置并清空已取回历史、浏览光标和搜索状态 → 输出为空,但内部状态被重置。

调用关系:外层的 set_mentions_v2_enabled 会调用它;它影响之后 on_entry_response 如何把旧历史文字变成 HistoryEntry。

调用图:被 1 处调用(set_mentions_v2_enabled)。

ChatComposerHistory::set_metadata261–272 ↗
fn set_metadata(&mut self, thread_id: ThreadId, log_id: u64, entry_count: usize)

作用:告诉历史管理器:当前会话对应哪条旧历史日志,以及旧日志里有多少条。没有这些信息,就不能安全地去取跨会话历史。

数据流:输入线程编号、日志编号、旧记录数量 → 保存这些元数据,并清空缓存、本地记录、重放记录、导航和搜索状态 → 输出为空,历史空间从新会话重新开始。

调用关系:外层 set_history_metadata 在会话配置好后调用它;之后 navigate_up、search 和 on_entry_response 都依赖这些元数据判断请求是否有效。

调用图:被 1 处调用(set_history_metadata)。

ChatComposerHistory::record_local_submission278–280 ↗
fn record_local_submission(&mut self, entry: HistoryEntry)

作用:记录用户本次会话刚提交的一条内容,方便稍后按上键找回。它保留完整草稿信息,不只是文字。

数据流:输入一个 HistoryEntry → 转交给 record_local_submission_inner 做过滤、去重和保存 → 输出为空,但可能把记录追加到 local_history。

调用关系:提交消息、记录待执行斜杠命令、Ctrl+C 清理前保存草稿等地方会调用它;它是本地历史进入系统的公开入口。

调用图:调用 1 个内部函数(record_local_submission_inner);被 3 处调用(clear_for_ctrl_c, prepare_submission_text_with_options, record_pending_slash_command_history)。

ChatComposerHistory::record_replayed_submission282–286 ↗
fn record_replayed_submission(&mut self, entry: HistoryEntry)

作用:记录从恢复的对话 transcript 里重放出来的用户消息。它既作为本地历史可翻,又标记为“重放种子”,以后可避免和持久化历史重复显示。

数据流:输入一个 HistoryEntry → 先按普通本地提交规则保存;如果真的保存成功,再复制一份放进 replay_seeded_history → 输出为空,内部可能增加两处记录。

调用关系:record_replayed_user_message_history 会调用它;populate_history_at_index 和 on_entry_response 会用 replay_seeded_history 判断旧历史是否和重放记录重复。

调用图:调用 1 个内部函数(record_local_submission_inner);被 1 处调用(record_replayed_user_message_history);外部调用 1 个(clone)。

ChatComposerHistory::record_local_submission_inner288–310 ↗
fn record_local_submission_inner(&mut self, entry: HistoryEntry) -> bool

作用:真正执行“保存一条本地历史”的内部函数。它会丢掉空记录,并避免连续保存完全相同的记录。

数据流:输入一个 HistoryEntry → 检查文字、图片、提及、粘贴等是否全空;清掉当前浏览和搜索状态;再和上一条比较避免连续重复 → 返回是否成功保存,并可能追加到 local_history。

调用关系:record_local_submission 和 record_replayed_submission 都把具体保存工作交给它;这样两种来源共享同一套去空、去重规则。

调用图:被 2 处调用(record_local_submission, record_replayed_submission)。

ChatComposerHistory::reset_navigation317–322 ↗
fn reset_navigation(&mut self)

作用:重置普通上下键浏览状态,让下一次按上键重新从最新历史开始。它也会关闭当前 Ctrl+R 搜索状态。

数据流:不需要输入 → 清空 history_cursor、pending_navigation_direction、last_history_text 和 search → 输出为空,历史浏览回到未开始状态。

调用关系:clear_for_ctrl_c 会调用它;之后 navigate_up 会把用户带回最新一条,而不是沿用旧光标。

调用图:被 1 处调用(clear_for_ctrl_c)。

ChatComposerHistory::should_handle_navigation343–361 ↗
fn should_handle_navigation(&self, text: &str, cursor: usize) -> bool

作用:判断这次 Up/Down 按键应不应该用来翻历史,而不是在多行文本里移动光标。这样用户编辑多行草稿时不会被误打断。

数据流:输入当前输入框文字和光标位置 → 先看有没有历史;空输入直接允许;非空时要求文字正好等于上次召回的历史,并且光标在开头或结尾 → 输出 true 或 false。

调用关系:handle_key_event_without_popup 和 sync_popups 会先问它;只有它说可以,外层才会调用 navigate_up 或 navigate_down。

调用图:被 2 处调用(handle_key_event_without_popup, sync_popups);外部调用 1 个(matches!)。

ChatComposerHistory::navigate_up368–387 ↗
fn navigate_up(&mut self, app_event_tx: &AppEventSender) -> Option<HistoryEntry>

作用:处理按上键:往更旧的历史走一步。能马上拿到的记录直接返回,旧历史还没取回时先发请求并返回空。

数据流:输入事件发送器 app_event_tx → 关闭搜索,计算下一条更旧的全局位置 → 调用 populate_history_at_index 取记录或发查询事件 → 输出可能是一条 HistoryEntry,也可能是 None 表示正在等旧历史。

调用关系:handle_key_event_without_popup 在用户按 Up 时调用它;它把具体取记录、跳重复、异步查询的活交给 populate_history_at_index。

调用图:调用 1 个内部函数(populate_history_at_index);被 1 处调用(handle_key_event_without_popup)。

ChatComposerHistory::navigate_down394–424 ↗
fn navigate_down(&mut self, app_event_tx: &AppEventSender) -> Option<HistoryEntry>

作用:处理按下键:往更新的历史走一步;如果已经越过最新记录,就返回一条空记录,表示应该清空输入框。

数据流:输入事件发送器 app_event_tx → 关闭搜索,按当前光标算下一条更新位置 → 有位置就调用 populate_history_at_index;没有位置就退出浏览并返回空 HistoryEntry → 输出记录、空记录或 None。

调用关系:handle_key_event_without_popup 在用户按 Down 时调用它;它和 navigate_up 一起实现类似 shell 的历史浏览体验。

调用图:调用 1 个内部函数(populate_history_at_index);被 1 处调用(handle_key_event_without_popup);外部调用 2 个(new, new)。

ChatComposerHistory::on_entry_response433–502 ↗
fn on_entry_response(
        &mut self,
        log_id: u64,
        offset: usize,
        entry: Option<String>,
        app_event_tx: &AppEventSender,
    ) -> HistoryEntryResponse

作用:处理异步取回的旧历史结果。它要判断这条回复是不是当前还需要的,不能让过期回复把输入框改错。

数据流:输入日志编号、历史偏移、可选文字、事件发送器 → 先拒绝日志编号不匹配的旧回复;把文字转成 HistoryEntry 并放入缓存;如果当前在搜索,就继续搜索流程;如果当前在普通导航,就返回召回结果或跳过重复 → 输出 Found、Search 或 Ignored。

调用关系:外层 on_history_entry_response 收到后台历史查询结果后调用它;它会继续调用 advance_search_after、search_match、populate_history_at_index 等函数接上之前暂停的流程。

调用图:调用 7 个内部函数(advance_search_after, next_history_offset, persistent_entry_duplicates_local, populate_history_at_index, search_match, search_matches, search_result_is_unique);被 1 处调用(on_history_entry_response);外部调用 2 个(Found, Search)。

ChatComposerHistory::total_entries586–588 ↗
fn total_entries(&self) -> usize

作用:计算当前能被看作一个整体的历史总条数。这个总数等于旧历史条数加本次会话新历史条数。

数据流:读取 persistent_entry_count 和 local_history 长度 → 相加 → 输出总条数。

调用关系:search 和 advance_search_from 用它判断扫描边界;它统一了“旧记录 + 新记录”的坐标空间。

调用图:被 2 处调用(advance_search_from, search)。

ChatComposerHistory::search_start_offset590–618 ↗
fn search_start_offset(
        &self,
        total_entries: usize,
        direction: HistorySearchDirection,
        restart: bool,
    ) -> Option<usize>

作用:为一次搜索计算从哪条历史开始找。向旧找和向新找的起点不同,重新搜索和继续搜索也不同。

数据流:输入总条数、方向、是否重开 → 读取当前搜索选中的偏移 → 根据方向返回最新、最旧、上一条或下一条的位置;如果越界就返回 None。

调用关系:search 在真正扫描前调用它;返回的起点随后交给 advance_search_from。

调用图:被 1 处调用(search)。

ChatComposerHistory::advance_search_after620–643 ↗
fn advance_search_after(
        &mut self,
        offset: usize,
        direction: HistorySearchDirection,
        boundary_if_exhausted: bool,
        app_event_tx: &AppEventSender,
    ) -> Histo

作用:当一个异步旧历史回复回来后,从它旁边的下一条继续搜索。这样搜索不会因为中间要等磁盘或后台而断掉。

数据流:输入刚处理过的偏移、方向、边界规则、事件发送器 → 算出下一条偏移;没有下一条就给出边界或未找到;有下一条就调用 advance_search_from 继续扫 → 输出搜索结果。

调用关系:on_entry_response 在等待中的搜索收到回复后调用它;它把后续扫描交回 advance_search_from。

调用图:调用 2 个内部函数(advance_search_from, exhausted_search_result);被 1 处调用(on_entry_response);外部调用 1 个(matches!)。

ChatComposerHistory::advance_search_from645–689 ↗
fn advance_search_from(
        &mut self,
        mut offset: usize,
        direction: HistorySearchDirection,
        boundary_if_exhausted: bool,
        app_event_tx: &AppEventSender,
    ) -> Hi

作用:从某个位置开始,按方向一条条扫描历史,直到找到匹配、需要等待旧历史、或走到尽头。

数据流:输入起始偏移、方向、边界规则、事件发送器 → 循环读取缓存中的记录;匹配且没重复就登记为命中;如果缺的是旧历史,就发送 LookupMessageHistoryEntry 请求并标记 awaiting;否则继续移动 → 输出 Found、Pending 或 NotFound。

调用关系:search 和 advance_search_after 都用它做实际扫描;它会调用 entry_at_cached_offset、search_matches、search_result_is_unique、search_match,并通过 app_event_tx.send 请求缺失历史。

调用图:调用 6 个内部函数(send, entry_at_cached_offset, search_match, search_matches, search_result_is_unique, total_entries);被 2 处调用(advance_search_after, search)。

ChatComposerHistory::entry_at_cached_offset691–699 ↗
fn entry_at_cached_offset(&self, offset: usize) -> Option<HistoryEntry>

作用:按统一偏移读取一条已经在内存里的历史。它不会主动去取缺失的旧历史。

数据流:输入一个全局偏移 → 如果偏移落在本地历史范围,就从 local_history 复制;如果落在旧历史范围,就查 fetched_history 缓存 → 输出可选 HistoryEntry。

调用关系:advance_search_from 和 populate_history_at_index 用它先尝试快速取记录;取不到时才考虑发送异步查询。

调用图:被 2 处调用(advance_search_from, populate_history_at_index)。

ChatComposerHistory::search_matches701–706 ↗
fn search_matches(&self, entry: &HistoryEntry) -> bool

作用:判断一条历史是否符合当前搜索词。搜索不区分大小写,空搜索词会匹配所有记录。

数据流:输入一条 HistoryEntry → 读取当前 search 状态;没有搜索状态就返回 false;有搜索状态就把历史文字转小写后检查是否包含查询词 → 输出 true 或 false。

调用关系:advance_search_from 扫描时调用它;on_entry_response 收到等待中的旧历史时也用它判断这条回复是否就是搜索结果。

调用图:被 2 处调用(advance_search_from, on_entry_response)。

ChatComposerHistory::search_result_is_unique708–712 ↗
fn search_result_is_unique(&self, entry: &HistoryEntry) -> bool

作用:判断搜索结果是不是本次搜索里还没展示过的文字。这样同样的命令不会在 Ctrl+R 里反复出现。

数据流:输入一条 HistoryEntry → 查看当前搜索状态的 seen_texts 集合 → 如果没有搜索状态或这段文字没见过,就返回 true;否则返回 false。

调用关系:advance_search_from 和 on_entry_response 在确认匹配后调用它;只有唯一结果才会交给 search_match。

调用图:被 2 处调用(advance_search_from, on_entry_response)。

ChatComposerHistory::search_match714–725 ↗
fn search_match(&mut self, offset: usize, entry: HistoryEntry) -> HistorySearchResult

作用:把一条搜索命中的历史设为当前选中项,并返回给界面展示。它同时更新普通历史光标,保证之后上下键位置一致。

数据流:输入命中的偏移和 HistoryEntry → 设置 history_cursor 和 last_history_text;在搜索状态里记录这个唯一匹配、清掉 awaiting、重置边界标记 → 输出 HistorySearchResult::Found。

调用关系:advance_search_from 找到匹配时调用它;on_entry_response 收到等待的匹配旧历史时也调用它。

调用图:被 2 处调用(advance_search_from, on_entry_response);外部调用 1 个(Found)。

ChatComposerHistory::select_cached_unique_match727–750 ↗
fn select_cached_unique_match(
        &mut self,
        direction: HistorySearchDirection,
    ) -> Option<HistorySearchResult>

作用:在已经找到过的搜索结果里切换,不重新扫描历史。比如用户在 Ctrl+R 里向旧找过后,再向新回来,就可以直接取缓存。

数据流:输入方向 → 根据当前 selected_match_index 算下一项;取出 unique_matches 中缓存的记录;更新 history_cursor、last_history_text 和搜索选中项 → 输出 Found,若没有可选项则输出 None。

调用关系:search 在继续同一个查询时优先调用它;只有缓存里没有目标结果,才会继续调用 advance_search_from 扫描。

调用图:被 1 处调用(search);外部调用 1 个(Found)。

ChatComposerHistory::exhausted_search_result752–769 ↗
fn exhausted_search_result(
        &mut self,
        direction: HistorySearchDirection,
        boundary_if_exhausted: bool,
    ) -> HistorySearchResult

作用:处理搜索走到头的情况。它区分“完全没找到”和“当前结果还有效,只是不能再往那边走了”。

数据流:输入方向和是否应当保持边界 → 清掉等待状态;如果需要保持边界,就在搜索状态里标记该方向已耗尽 → 输出 AtBoundary 或 NotFound。

调用关系:search 和 advance_search_after 在没有下一条可扫时调用它;它防止重复撞边界时又发无意义的旧历史查询。

调用图:被 2 处调用(advance_search_after, search)。

ChatComposerHistory::populate_history_at_index771–810 ↗
fn populate_history_at_index(
        &mut self,
        global_idx: usize,
        direction: HistorySearchDirection,
        app_event_tx: &AppEventSender,
    ) -> Option<HistoryEntry>

作用:按指定全局位置取一条历史给普通上下键浏览使用。它会跳过和重放记录重复的旧历史,并在需要时发异步查询。

数据流:输入目标偏移、方向、事件发送器 → 先查缓存或本地记录;如果命中且不是应跳过的重复旧记录,就返回;如果重复就按方向找下一条;如果缺失旧历史,就发送 LookupMessageHistoryEntry 并记录等待方向 → 输出可选 HistoryEntry。

调用关系:navigate_up、navigate_down 和 on_entry_response 都依赖它取普通导航结果;它会调用 entry_at_cached_offset、persistent_entry_duplicates_local、next_history_offset 和 send。

调用图:调用 4 个内部函数(send, entry_at_cached_offset, next_history_offset, persistent_entry_duplicates_local);被 3 处调用(navigate_down, navigate_up, on_entry_response)。

ChatComposerHistory::next_history_offset812–823 ↗
fn next_history_offset(
        &self,
        offset: usize,
        direction: HistorySearchDirection,
    ) -> Option<usize>

作用:按方向计算下一条历史的位置,并负责防止越界。它是历史列表里的“小步前进/后退”工具。

数据流:输入当前偏移和方向 → 向旧时减一,向新时加一并检查是否小于总条数 → 输出下一偏移或 None。

调用关系:populate_history_at_index 跳过重复记录时用它;on_entry_response 发现取回的旧历史和本地重放重复时也用它继续走。

调用图:被 2 处调用(on_entry_response, populate_history_at_index)。

ChatComposerHistory::persistent_entry_duplicates_local825–829 ↗
fn persistent_entry_duplicates_local(&self, entry: &HistoryEntry) -> bool

作用:判断一条旧历史是否已经通过恢复 transcript 作为本地记录出现过。这样恢复会话后不会看到两份一模一样的历史。

数据流:输入一条旧 HistoryEntry → 遍历 replay_seeded_history,比对文字和提及绑定 → 输出 true 表示重复,false 表示不重复。

调用关系:populate_history_at_index 和 on_entry_response 用它决定是否跳过持久化记录;它只针对重放种子记录,不会删除真实历史。

调用图:被 2 处调用(on_entry_response, populate_history_at_index)。

HistorySearchState::new833–845 ↗
fn new(query: &str) -> Self

作用:创建一次新的 Ctrl+R 搜索状态。它保存原查询词、小写查询词、已见过的结果和当前位置。

数据流:输入查询字符串 → 保存原文和小写版本,清空匹配列表、已见文本、等待状态和边界标记 → 输出一个新的 HistorySearchState。

调用关系:ChatComposerHistory::search 在打开搜索、修改查询词或要求重开搜索时调用它。

调用图:被 1 处调用(search);外部调用 2 个(new, new)。

HistorySearchState::is_exhausted847–852 ↗
fn is_exhausted(&self, direction: HistorySearchDirection) -> bool

作用:检查某个搜索方向是否已经确认没有更多结果。这样重复按同一方向时不用再白扫一遍。

数据流:输入方向 → 读取 exhausted_older 或 exhausted_newer → 输出 true 或 false。

调用关系:ChatComposerHistory::search 会用它判断是否直接返回 AtBoundary;它配合 mark_exhausted 使用。

HistorySearchState::mark_exhausted854–859 ↗
fn mark_exhausted(&mut self, direction: HistorySearchDirection)

作用:标记某个搜索方向已经走到头。之后同方向继续搜索可以立即告诉用户到边界了。

数据流:输入方向 → 把 exhausted_older 或 exhausted_newer 设为 true → 输出为空,改变搜索状态。

调用关系:ChatComposerHistory::exhausted_search_result 在需要保持当前匹配但表示撞边界时调用它。

HistorySearchState::record_match861–883 ↗
fn record_match(&mut self, offset: usize, entry: &HistoryEntry)

作用:把一个新的唯一搜索命中加入缓存,或者选中已经缓存过的同一偏移。缓存按从新到旧的顺序保存。

数据流:输入命中偏移和条目 → 如果该偏移已在 unique_matches 中,就直接 select_match;否则把文本加入 seen_texts,按偏移顺序插入 UniqueHistoryMatch,再选中新插入项 → 输出为空,更新搜索缓存和选中位置。

调用关系:ChatComposerHistory::search_match 调用它登记搜索结果;它再调用 select_match 完成选中。

调用图:调用 1 个内部函数(select_match);外部调用 1 个(clone)。

HistorySearchState::select_match885–894 ↗
fn select_match(&mut self, index: usize)

作用:选中缓存里的某个搜索结果。它会同步原始偏移和缓存下标,并清除等待状态。

数据流:输入匹配列表下标 → 如果下标有效,就读取对应 UniqueHistoryMatch,设置 selected_offset 和 selected_match_index,清掉 awaiting 和边界标记 → 输出为空。

调用关系:HistorySearchState::record_match 会调用它;ChatComposerHistory::select_cached_unique_match 也会在切换缓存结果时调用它。

调用图:被 1 处调用(record_match)。

tests::test_thread_id904–907 ↗
fn test_thread_id() -> ThreadId

作用:测试用的固定线程编号生成器。它让多个测试都使用同一个合法的 ThreadId。

数据流:不需要输入 → 从固定字符串解析 ThreadId → 输出测试线程编号,解析失败会让测试直接失败。

调用关系:多个测试调用它来设置 set_metadata 或校验发出的历史查询事件。

调用图:调用 1 个内部函数(from_string)。

tests::duplicate_submissions_are_not_recorded910–936 ↗
fn duplicate_submissions_are_not_recorded()

作用:验证空提交不会入历史,连续重复提交也不会重复保存。这样历史列表不会被无意义内容刷屏。

数据流:创建空历史管理器 → 依次记录空文本、hello、重复 hello、world → 用断言检查 local_history 的长度和最后一条内容 → 测试通过表示去空去重规则正常。

调用关系:它主要覆盖 ChatComposerHistory::record_local_submission 和 HistoryEntry::new 的行为。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, new)。

tests::persistent_restore_gates_at_mentions939–1005 ↗
fn persistent_restore_gates_at_mentions()

作用:验证旧历史恢复 @ 提及时会受开关控制。不开启时 @ 风格会按兼容规则变成 $,开启后保留 @。

数据流:创建历史管理器并设置一条旧历史 → 先在默认关闭状态下请求并注入回复,检查恢复结果;再打开开关、重新请求并注入同样文字,检查 @ 绑定是否保留 → 测试输出是断言结果。

调用关系:它覆盖 set_at_mention_restore_enabled、navigate_up、on_entry_response 和 HistoryEntry::new_with_at_mentions 的配合。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, assert_eq!, test_thread_id)。

tests::navigation_with_async_fetch1008–1079 ↗
fn navigation_with_async_fetch()

作用:验证普通上键浏览遇到旧历史时会先发查询,等回复回来后再显示。它模拟真实异步取历史的流程。

数据流:创建带 3 条旧历史和 1 条本地历史的管理器 → 第一次上键取本地最新;第二次上键发 offset 2 查询;注入回复后得到结果;再上键发 offset 1 查询并注入回复 → 用断言检查事件和返回值。

调用关系:它覆盖 navigate_up、populate_history_at_index、on_entry_response 以及 AppEvent::LookupMessageHistoryEntry 的发送。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(assert!, assert_eq!, new, panic!, test_thread_id)。

tests::search_matches_local_history_and_stops_at_boundaries1082–1145 ↗
fn search_matches_local_history_and_stops_at_boundaries()

作用:验证 Ctrl+R 能在本地历史里找到匹配,并且到最旧或最新边界时返回 AtBoundary。边界不会把隐藏光标继续推进。

数据流:记录三条本地历史 → 搜索 git 向旧找到 git diff,再找到 git status,再继续得到 AtBoundary;向新回到 git diff,再撞到边界 → 断言每一步结果。

调用关系:它主要覆盖 ChatComposerHistory::search、select_cached_unique_match 和 exhausted_search_result。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, new)。

tests::search_skips_duplicate_local_matches1148–1203 ↗
fn search_skips_duplicate_local_matches()

作用:验证搜索会跳过本地历史里文字相同的重复结果。用户看到的是不同文本,而不是同一句话反复出现。

数据流:记录包含两个 git status 的本地历史 → 搜索 git 向旧时先得到 git diff,再得到一次 git status,然后到边界;再向新和向旧切换 → 断言重复项没有作为新结果出现。

调用关系:它覆盖 search_result_is_unique、HistorySearchState::record_match 和缓存切换逻辑。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, new)。

tests::repeated_boundary_search_does_not_refetch_persistent_history1206–1276 ↗
fn repeated_boundary_search_does_not_refetch_persistent_history()

作用:验证搜索旧历史撞到边界后,再重复按同方向不会再次发查询。这样不会浪费后台请求。

数据流:设置 3 条旧历史 → 搜索 needle,先取到最新匹配;继续向旧依次收到不匹配回复直到边界;再次向旧搜索 → 断言返回 AtBoundary 且事件通道没有新请求。

调用关系:它覆盖 pending 搜索、on_entry_response、advance_search_after 和 exhausted 方向标记。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, assert_eq!, test_thread_id)。

tests::search_fetches_persistent_history_until_match1279–1340 ↗
fn search_fetches_persistent_history_until_match()

作用:验证搜索会一条条请求旧历史,直到找到匹配项。中间不匹配的旧记录不会中断搜索。

数据流:设置 3 条旧历史 → 搜索 older 先请求最新 offset 2;注入不匹配 latest 后继续请求 offset 1;注入 older command 后返回 Found → 断言请求顺序和结果。

调用关系:它覆盖 advance_search_from 发送查询、on_entry_response 接续扫描和 search_match 返回命中。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, panic!, test_thread_id)。

tests::search_skips_duplicate_persistent_matches1343–1431 ↗
fn search_skips_duplicate_persistent_matches()

作用:验证搜索旧历史时也会跳过文字重复的匹配。即使重复项来自持久化历史,也只展示一次。

数据流:设置 4 条旧历史 → 搜索 needle 先命中 needle same;继续向旧时遇到重复 needle same 被跳过,再跳过不匹配,最后命中 needle older;到边界后向新回到 needle same → 用断言检查结果。

调用关系:它覆盖 search_result_is_unique、on_entry_response 和缓存中向新选择已发现结果的流程。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, test_thread_id)。

tests::search_is_case_insensitive_and_empty_query_finds_latest1434–1459 ↗
fn search_is_case_insensitive_and_empty_query_finds_latest()

作用:验证搜索不区分大小写,并且空查询会选中最新历史。这样 Ctrl+R 的行为更接近常见命令行。

数据流:记录 Build Release → 用小写 release 搜索应命中;再用空字符串搜索也应命中同一条 → 断言返回 Found。

调用关系:它覆盖 search_matches 里的小写匹配和空查询匹配规则。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, new)。

tests::reset_navigation_resets_cursor1462–1492 ↗
fn reset_navigation_resets_cursor()

作用:验证重置普通导航后,下一次上键会重新从最新记录开始,而不是接着旧位置走。

数据流:准备 3 条旧历史中的两条缓存 → 上键两次到较旧记录 → 调用 reset_navigation → 检查光标和 last_history_text 清空;再上键应回到最新缓存记录 → 断言结果。

调用关系:它覆盖 reset_navigation、navigate_up 和缓存历史读取的配合。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(assert!, assert_eq!, new, test_thread_id)。

tests::should_handle_navigation_when_cursor_is_at_line_boundaries1495–1504 ↗
fn should_handle_navigation_when_cursor_is_at_line_boundaries()

作用:验证只有光标在文本开头或结尾,并且文本等于刚召回的历史时,非空输入才会继续翻历史。

数据流:创建历史并记录 hello,把 last_history_text 设为 hello → 分别测试光标在开头、结尾、中间以及文本不同的情况 → 输出断言结果。

调用关系:它覆盖 should_handle_navigation,确保外层按键处理不会误把多行编辑当成历史导航。

调用图:调用 1 个内部函数(new);外部调用 2 个(assert!, new)。

tui/src/bottom_pane/chat_composer/history_search.rs源码 ↗
domain_logic用户按键处理和界面渲染期间

这个文件处理的是输入框里的“倒着搜历史记录”,类似很多终端里按 Ctrl+R 找以前命令。它自己不负责判断哪条历史匹配,这件事交给 ChatComposerHistory;它负责更贴近界面的部分:进入搜索时先保存当前草稿,把搜索词放到底部提示栏里,而不是直接写进正文;找到匹配时把历史内容临时放进输入框预览;按 Enter 才真正接受;按 Esc 或 Ctrl+C 就恢复原来的草稿。这样用户可以放心试搜,不会误删正在写的内容。它还负责画底部提示文字、计算搜索词光标位置,以及把匹配到的文字高亮出来。一个重要细节是:刚按 Ctrl+R 时不会立刻拿最新历史覆盖输入框,必须先输入搜索词。

函数细节27
ChatComposer::history_search_active79–81 ↗
fn history_search_active(&self) -> bool

作用:这是测试用的小检查,回答“现在是不是正在历史搜索”。它让测试可以直接确认 Ctrl+R 搜索模式有没有打开。

数据流:它读取 ChatComposer 里保存搜索会话的字段 → 看这个字段是不是有值 → 返回 true 或 false,不改动任何东西。

调用关系:它只在测试配置下存在。测试在模拟用户按 Ctrl+R、Enter、Esc 后调用它,确认搜索模式是否按预期进入或退出。

ChatComposer::is_history_search_key89–91 ↗
fn is_history_search_key(key_event: &KeyEvent, bindings: &[KeyBinding]) -> bool

作用:判断某个按键是不是“开始反向历史搜索”或“继续往更老的历史里找”的快捷键。这样普通输入处理就不会把 Ctrl+R 当成奇怪字符塞进文本里。

数据流:输入一个按键事件和一组快捷键绑定 → 交给绑定列表的 is_pressed 判断是否命中 → 输出 true 或 false,不改变输入框状态。

调用关系:它会被按键处理流程提前调用,也会在搜索模式里的 handle_history_search_key 中使用。真正的匹配判断交给 is_pressed。

调用图:调用 1 个内部函数(is_pressed)。

ChatComposer::is_history_search_forward_key93–95 ↗
fn is_history_search_forward_key(key_event: &KeyEvent, bindings: &[KeyBinding]) -> bool

作用:判断某个按键是不是“往较新的历史结果走”的快捷键。它对应反向搜索里的反方向移动,比如 Ctrl+S 或向下键这类行为。

数据流:输入按键事件和下一条匹配的快捷键绑定 → 用 is_pressed 检查是否按下 → 返回是否命中,不改状态。

调用关系:它主要服务 handle_history_search_key。搜索模式收到按键后,用它决定是否要把搜索方向交给 history_search_in_direction 的“较新”方向。

调用图:调用 1 个内部函数(is_pressed)。

ChatComposer::handle_history_search_key134–231 ↗
fn handle_history_search_key(&mut self, key_event: KeyEvent) -> (InputResult, bool)

作用:这是搜索模式里的总按键入口。只要底部栏正在当搜索框用,用户按的键都先到这里决定是改搜索词、移动结果、接受,还是取消。

数据流:输入一个按键事件 → 如果是按键释放就忽略;如果是 Ctrl+R 或向上键就找更老的匹配;如果是向下方向快捷键就找更新的匹配;Esc 或 Ctrl+C 取消并恢复草稿;Enter 在已有匹配时接受预览;退格、Ctrl+U、普通字符则修改底部搜索词 → 输出 InputResult 和是否消费这个按键,同时可能改搜索会话、草稿、底部模式和历史游标。

调用关系:它是搜索模式期间的分发员。它会把方向移动交给 history_search_in_direction,把搜索词变化交给 update_history_search_query,把取消交给 cancel_history_search,并用 is_history_search_key、is_history_search_forward_key、has_ctrl_or_alt 来区分不同按键。

调用图:调用 5 个内部函数(cancel_history_search, history_search_in_direction, update_history_search_query, reset_mode_after_activity, has_ctrl_or_alt);外部调用 4 个(is_history_search_forward_key, is_history_search_key, new, matches!)。

ChatComposer::history_search_in_direction233–257 ↗
fn history_search_in_direction(&mut self, direction: HistorySearchDirection) -> InputResult

作用:按当前搜索词,在历史结果里向某个方向移动一次。比如再按一次 Ctrl+R 找更老的匹配,或按向下找更新的匹配。

数据流:它读取当前搜索词和进入搜索前保存的草稿 → 如果搜索词为空,就重置搜索并恢复原草稿;如果不为空,就让历史模块按指定方向继续查找 → 把查找结果交给 apply_history_search_result 更新界面,最后返回没有额外输入结果。

调用关系:它只由 handle_history_search_key 在用户按方向类按键时调用。它不自己解释结果,而是把 Found、Pending、NotFound 等结果交给 apply_history_search_result。

调用图:调用 1 个内部函数(apply_history_search_result);被 1 处调用(handle_history_search_key)。

ChatComposer::update_history_search_query259–286 ↗
fn update_history_search_query(&mut self, query: String)

作用:更新底部历史搜索框里的搜索词,并从头开始按新词搜索。用户输入字符、退格或 Ctrl+U 清空时会用到它。

数据流:输入新的搜索词 → 读取原始草稿,先把搜索会话里的 query 改成新值并标成正在搜索 → 恢复原草稿,避免旧预览残留;如果搜索词为空就重置并停在空闲状态;否则从最新历史开始重新查找 → 把结果交给 apply_history_search_result。

调用关系:它由 handle_history_search_key 在编辑搜索词时调用。它会调用历史模块 search,然后把结果交给 apply_history_search_result 统一更新预览和状态。

调用图:调用 1 个内部函数(apply_history_search_result);被 1 处调用(handle_history_search_key)。

ChatComposer::apply_history_search_result311–342 ↗
fn apply_history_search_result(&mut self, result: HistorySearchResult)

作用:把历史模块返回的搜索结果翻译成用户能看到的界面变化。找到就预览,没找到就恢复草稿,等待中就显示正在搜。

数据流:输入一个 HistorySearchResult → 如果 Found,就把状态设为匹配并把历史条目放进输入框预览;如果 Pending,就设为正在搜索;如果 AtBoundary,就保持匹配状态不闪成“没找到”;如果 NotFound,就设为无匹配并恢复原始草稿 → 不返回值,但会改搜索状态和输入框内容。

调用关系:history_search_in_direction 和 update_history_search_query 都把搜索结果交给它。它是历史查找和界面预览之间的转换层。

调用图:被 2 处调用(history_search_in_direction, update_history_search_query)。

ChatComposer::history_search_action_key_span372–374 ↗
fn history_search_action_key_span(key: KeyCode) -> Span<'static>

作用:把一个按键名做成底部提示里醒目的小标签。比如把 Enter 显示成蓝色加粗的“enter”。

数据流:输入一个 KeyCode → 先用 key_hint::plain 转成普通人能读的按键名字 → 包装成 Span,并加上蓝色、加粗、不变暗的样式 → 返回这段可渲染文本。

调用关系:history_search_footer_line 在显示“接受”和“取消”提示时调用它。它专门负责按键标签的视觉样式。

调用图:调用 1 个内部函数(plain);外部调用 1 个(from)。

ChatComposer::history_search_highlight_ranges381–389 ↗
fn history_search_highlight_ranges(&self) -> Vec<Range<usize>>

作用:告诉渲染器:当前预览内容里哪些位置应该高亮。这样用户能看出历史条目为什么匹配了搜索词。

数据流:它读取搜索会话状态、搜索词和当前输入框文字 → 只有处于匹配状态且搜索词不为空时,才计算大小写不敏感的匹配范围 → 返回一组字节范围;其他情况返回空列表。

调用关系:输入框渲染时会用它决定哪些字要反色加粗。真正查找范围的细节交给 case_insensitive_match_ranges。

调用图:外部调用 3 个(case_insensitive_match_ranges, new, matches!)。

ChatComposer::case_insensitive_match_ranges391–438 ↗
fn case_insensitive_match_ranges(text: &str, query: &str) -> Vec<Range<usize>>

作用:在一段文字里找出所有“不区分大小写”的搜索词位置。它特别小心处理 Unicode 字符,也就是不只照顾英文。

数据流:输入原文 text 和搜索词 query → 先把搜索词转成小写形式;再把原文逐字符转小写,同时记住小写后的字符对应原文哪个字节范围;然后在小写文本里查找所有匹配 → 输出原文里的字节范围列表。输入为空或转换后为空时输出空列表。

调用关系:history_search_highlight_ranges 调用它来得到高亮位置。测试也直接验证它能处理大小写和特殊字符。

调用图:外部调用 2 个(new, new)。

ChatComposer::history_search_cursor_pos445–482 ↗
fn history_search_cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:计算搜索模式下屏幕光标该放在哪里。光标跟着底部搜索词末尾走,而不是跟着输入框里的历史预览走。

数据流:输入整个组件可用的屏幕区域 → 读取搜索词、布局区域、底部高度和缩进 → 找到底部提示所在的小矩形,计算“reverse-i-search: ”加搜索词之后的位置,并把 x 坐标限制在可见区域内 → 返回屏幕坐标;如果没有搜索或区域太小,返回空。

调用关系:终端界面渲染完成后会用它设置真实光标位置。它依赖 layout_areas、footer_props、footer_height、footer_spacing 和 ratatui 的布局工具来算位置。

调用图:外部调用 4 个(Length, vertical, from, footer_spacing)。

tests::history_search_opens_without_previewing_latest_entry505–525 ↗
fn history_search_opens_without_previewing_latest_entry()

作用:这个测试确认:按 Ctrl+R 只会打开搜索模式,不会立刻把最新历史填进输入框。这样用户不会被突然覆盖草稿吓到。

数据流:它创建一个 composer,放入一条历史记录,并把输入框设为空 → 模拟按 Ctrl+R → 检查搜索已开启、输入框仍为空、底部模式变成历史搜索。

调用关系:它由测试运行器执行,验证 begin_history_search 与普通按键入口配合时的初始行为。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(Char, new, new, new, assert!, assert_eq!, new)。

tests::history_search_match_ranges_are_case_insensitive528–538 ↗
fn history_search_match_ranges_are_case_insensitive()

作用:这个测试确认高亮匹配不区分大小写,并且能处理特殊 Unicode 字符。它保护搜索高亮不会只对英文简单场景正确。

数据流:它直接输入几组原文和搜索词给 case_insensitive_match_ranges → 对比返回的范围是否符合预期 → 也检查空搜索词不会产生高亮。

调用关系:它由测试运行器执行,直接覆盖 case_insensitive_match_ranges 这个底层辅助函数。

调用图:外部调用 2 个(assert!, assert_eq!)。

tests::history_search_accepts_matching_entry541–576 ↗
fn history_search_accepts_matching_entry()

作用:这个测试确认:输入搜索词找到历史后,按 Enter 会接受这条历史,把它变成普通可编辑草稿。

数据流:它创建 composer,加入两条历史,把当前草稿设为 draft → Ctrl+R 后输入 git → 检查输入框预览 git status → 按 Enter → 检查搜索关闭、文本保留为 git status、光标移动到末尾。

调用关系:它通过 handle_key_event 间接覆盖 begin_history_search、update_history_search_query、apply_history_search_result 和 handle_history_search_key 的 Enter 分支。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, new)。

tests::vim_normal_history_search_preview_places_cursor_on_last_char579–602 ↗
fn vim_normal_history_search_preview_places_cursor_on_last_char()

作用:这个测试确认在 Vim 模式下,搜索预览出现时光标位置符合 Vim 的习惯:停在最后一个字符上,而不是末尾之后。

数据流:它创建 composer,加入 git status 历史,开启 Vim 模式 → Ctrl+R 后输入 git → 检查预览文本是 git status,并且光标在最后一个字符的位置。

调用关系:它由测试运行器执行,验证历史搜索预览和 composer 的 Vim 光标规则没有互相打架。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert_eq!, new)。

tests::history_search_stays_on_single_match_at_boundaries605–657 ↗
fn history_search_stays_on_single_match_at_boundaries()

作用:这个测试确认只有一个匹配结果时,反复向前或向后找不会变成“无匹配”。用户看到的预览应该稳定停在那条结果上。

数据流:它创建 composer,加入一条包含 bug 的历史,输入搜索词 bug 得到预览 → 多次按 Ctrl+R 和向下键 → 检查输入框一直是同一条历史,搜索状态仍是 Match。

调用关系:它验证 apply_history_search_result 对 AtBoundary 的处理。这个行为避免到达边界时界面闪烁成 no match。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, new)。

tests::history_search_highlights_matches_until_accepted721–774 ↗
fn history_search_highlights_matches_until_accepted()

作用:这个测试确认搜索预览时匹配词会高亮,但按 Enter 接受以后高亮会消失。因为接受后那就是普通草稿,不再是搜索预览。

数据流:它创建 composer,加入历史,搜索 git → 渲染界面到测试缓冲区 → 检查 git 三个字符被反色加粗,后面的字符没有;然后按 Enter 接受并重新渲染 → 检查高亮消失。

调用关系:它把 handle_key_event、history_search_highlight_ranges 和渲染流程串起来测,验证搜索状态清除后渲染也跟着变。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(empty, Char, new, new, assert!, assert_eq!, new)。

tests::history_search_esc_restores_original_draft777–802 ↗
fn history_search_esc_restores_original_draft()

作用:这个测试确认按 Esc 取消搜索会恢复进入搜索前的草稿和光标位置。它保护用户正在写的内容不被历史预览覆盖。

数据流:它创建 composer,加入历史,把草稿设为 draft 且光标放在位置 2 → 进入搜索并搜到 remembered command → 按 Esc → 检查搜索关闭、文本恢复 draft、光标也回到 2。

调用关系:它通过按键流程覆盖 cancel_history_search,确认取消搜索会使用 begin_history_search 保存的 original_draft。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, new)。

tests::history_search_ctrl_c_restores_original_draft805–842 ↗
fn history_search_ctrl_c_restores_original_draft()

作用:这个测试确认 Ctrl+C 的两种终端表示方式都能取消搜索并恢复草稿。不同终端可能用不同形式报告 Ctrl+C,所以这里都要守住。

数据流:它先用内部辅助创建一个已经预览历史的 composer → 分别发送 Ctrl+C 和原始控制字符 \u0003 → 检查搜索关闭、文本恢复 draft、光标恢复到原位置。

调用关系:它覆盖 handle_history_search_key 里的两个 Ctrl+C 分支,并间接验证 cancel_history_search。

调用图:外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::history_search_flushes_pending_first_char_before_snapshot845–870 ↗
fn history_search_flushes_pending_first_char_before_snapshot()

作用:这个测试确认:如果用户刚输入的字符还在“粘贴爆发”缓冲里,进入搜索前会先把它写进草稿再保存快照。

数据流:它创建 composer,模拟输入 h,使它暂存在粘贴缓冲而不是立即进入文本 → 按 Ctrl+R → 检查搜索已开、粘贴缓冲结束、草稿是 h → Esc 后仍然是 h。

调用关系:它验证 begin_history_search 开头处理 paste_burst 的逻辑,防止取消搜索时丢掉刚输入的字符。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::history_search_flushes_buffered_paste_before_snapshot873–908 ↗
fn history_search_flushes_buffered_paste_before_snapshot()

作用:这个测试确认整段快速输入被识别成粘贴时,进入历史搜索前也会先落到草稿里。否则取消搜索可能会丢掉刚粘贴的内容。

数据流:它按很短时间间隔输入 paste,让 composer 认为这是一次粘贴爆发 → 按 Ctrl+R → 检查草稿变成 paste 且粘贴状态结束 → Esc 后仍保留 paste。

调用关系:它覆盖 begin_history_search 对批量粘贴缓冲的处理,比单字符测试更接近真实粘贴场景。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(from_millis, now, Char, new, assert!, assert_eq!)。

tests::history_search_esc_resets_normal_history_navigation911–941 ↗
fn history_search_esc_resets_normal_history_navigation()

作用:这个测试确认取消搜索还会重置普通的历史上下导航。否则退出搜索后按上键可能从奇怪的位置继续跳。

数据流:它加入两条历史,搜索 match 预览较老的匹配 → 按 Esc 取消,确认输入框为空 → 再按向上键 → 检查拿到的是最新历史,而不是被搜索游标影响后的结果。

调用关系:它验证 cancel_history_search 调用 reset_navigation 的必要性,确保搜索导航和普通历史导航不会串台。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(Char, new, new, new, assert!, assert_eq!, new)。

tests::history_search_no_match_restores_preview_but_keeps_search_open944–967 ↗
fn history_search_no_match_restores_preview_but_keeps_search_open()

作用:这个测试确认搜不到时,输入框会恢复原草稿,但搜索模式不会关闭。用户可以继续改搜索词,而不用重新按 Ctrl+R。

数据流:它创建 composer,加入 git status 历史,把草稿设为 draft → Ctrl+R 后输入 zzz → 搜不到匹配 → 检查搜索仍然开启、输入框恢复 draft、底部仍是历史搜索模式。

调用关系:它覆盖 update_history_search_query 和 apply_history_search_result 的 NotFound 分支,保证“无匹配”是可继续编辑的状态。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, new)。

Slash 命令和 composer 流程

这些文件涵盖 slash 命令词汇和解析、命令选择弹窗支持,以及将消息输入串联起来的主聊天 composer 状态机。

tui/src/bottom_pane/prompt_args.rs源码 ↗
utilrequest handling

在这个界面里,用户可能会输入普通文字,也可能输入像 /name 参数 这样的斜杠命令。这个文件提供的解析工具,就是先检查一行文字是不是以 / 开头;如果是,就继续找出命令名在哪里结束,后面的内容从哪里开始。它还会跳过命令名后多余的空格,让后续代码拿到干净的参数。一个容易忽略但很重要的点是,它返回的不只是“命令名”和“参数”,还返回参数在原始字符串里的字节位置。这个位置像书签一样,后面如果要高亮、校验、截取或替换用户输入,就能准确找到原文中的那一段。没有这个小工具,各处代码可能会各自手写解析规则,结果很容易出现空格处理不一致、位置算错、命令误判等问题。

函数细节1
parse_slash_name7–25 ↗
fn parse_slash_name(line: &str) -> Option<(&str, &str, usize)>

作用:解析一行以 / 开头的命令,把它拆成命令名、命令后面的内容,以及后面内容在原始行里的起始位置。有人会用它来判断用户是不是输入了斜杠命令,并准确取出命令参数。

数据流:输入是一整行文字。函数先看它是不是以 / 开头;不是的话直接返回“没有命令”。如果是,它会从 / 后面开始找第一个空白字符,把前面当作命令名;如果命令名为空,也返回“没有命令”。然后它把命令名后面的内容去掉开头空格,算出这段内容在原始字符串中的字节下标,最后返回三样东西:命令名、整理后的剩余内容、剩余内容的原文起点位置。它不修改原字符串,只是返回指向原字符串的切片和一个位置数字。

调用关系:它是底部输入框处理流程里的基础小零件。用户提交内容时,handle_submission_with_time、validate_submission、prepared_args、submit_queued_slash_prompt 等流程会用它判断和准备斜杠命令;bare_command、inline_command、command_element_range 这类和命令识别、范围计算有关的代码也会用它。它自己不再调用别的项目函数,只负责把“原始输入”切成后续流程能理解的几块。

调用图:被 7 处调用(handle_submission_with_time, bare_command, command_element_range, inline_command, validate_submission, prepared_args, submit_queued_slash_prompt)。

tui/src/slash_command.rs源码 ↗
domain_logiccross-cutting

这个文件像一本“斜杠命令菜单说明书”。用户在聊天输入框里打 / 时,程序需要知道有哪些命令可以弹出来、每个命令是什么意思、哪些命令在当前状态下不能点。这里用 SlashCommand 这个枚举(就是一组固定选项的清单)列出所有内置命令,并用 strum 这类工具自动把命令和文字互相转换,比如把 Stop 变成“stop”,也能把用户输入的“clean”识别成 Stop。它还把命令分成几类:有的能接后面的文字参数,有的只在普通聊天里能用,有的即使任务正在跑也能用。另一个重要点是可见性:有些命令只在 Windows、macOS 或调试版本里显示,避免用户看到当前环境根本用不了的入口。文件底部的测试则保证一些别名和特殊命令名不会被改坏。

函数细节12
SlashCommand::description83–144 ↗
fn description(self) -> &'static str

作用:给某个斜杠命令返回一句给用户看的说明文字。弹出命令菜单时,用户靠这句话判断这个命令是干什么的。

数据流:进去的是一个具体命令,比如 SlashCommand::Review → 函数查表式地匹配它 → 出来的是一段固定英文说明,比如“review my current changes and find issues”。它不改任何状态,只负责提供说明。

调用关系:它是 SlashCommand 自己身上的说明牌。命令菜单或帮助界面需要展示命令含义时会用到这类信息;它不把工作交给别的项目函数,只是根据命令本身返回文字。

SlashCommand::command148–150 ↗
fn command(self) -> &'static str

作用:把程序内部的命令名字转换成用户实际输入时看到的命令文字,不带开头的“/”。比如 Stop 会变成“stop”。

数据流:进去的是一个 SlashCommand 值 → 借助枚举到字符串的转换规则,把它变成静态字符串 → 出来的是命令名文本。它不保存东西,也不修改东西。

调用关系:dispatch_prepared_command_with_args、ensure_side_command_allowed_outside_review、request_empty_side_conversation 会在分发命令、检查命令或提示用户时调用它。它像统一的取名窗口,避免不同地方自己拼命令名导致不一致。

调用图:被 3 处调用(dispatch_prepared_command_with_args, ensure_side_command_allowed_outside_review, request_empty_side_conversation)。

SlashCommand::supports_inline_args153–171 ↗
fn supports_inline_args(self) -> bool

作用:判断一个命令后面能不能直接跟参数。比如 /review 后面可以带说明,某些开关类命令则不适合这样用。

数据流:进去的是一个命令 → 函数检查它是否在“允许带参数”的名单里 → 出来 true 或 false。true 表示用户输入“/命令 后续文字”是被允许的,false 表示不支持这种写法。

调用关系:dispatch_command_with_args 在处理带参数的斜杠输入时会问它一句:这个命令能不能吃后面的文字?函数内部用 matches! 这个 Rust 匹配工具做名单判断,不继续调用项目里的其他流程。

调用图:被 1 处调用(dispatch_command_with_args);外部调用 1 个(matches!)。

SlashCommand::available_in_side_conversation174–185 ↗
fn available_in_side_conversation(self) -> bool

作用:判断某个命令在“旁支对话”里还能不能用。旁支对话可以理解成临时开的小分叉,不是主聊天,所以只开放少数安全命令。

数据流:进去的是一个命令 → 函数检查它是否属于旁支对话允许的短名单,比如复制、查看差异、提到文件等 → 出来 true 或 false。它只做判断,不执行命令。

调用关系:ensure_slash_command_allowed_in_side_conversation 会调用它来拦截不该在旁支对话里运行的命令。它自己只用 matches! 做快速判断,像门口保安看名单。

调用图:被 1 处调用(ensure_slash_command_allowed_in_side_conversation);外部调用 1 个(matches!)。

SlashCommand::available_during_task188–244 ↗
fn available_during_task(self) -> bool

作用:判断任务正在运行时,这个命令还能不能执行。这样可以避免用户在后台任务没结束时触发会破坏会话状态的操作。

数据流:进去的是一个命令 → 函数把它分到“任务中禁止”或“任务中允许”的组里 → 出来 true 或 false。比如查看状态、复制内容通常允许;新建会话、切模型、清空会话这类容易改变大局的操作通常禁止。

调用关系:dispatch_command 和 dispatch_command_with_args 在真正执行命令前会调用它。它位于命令分发的安全检查环节,先决定能不能放行,再让后续执行逻辑继续。

调用图:被 2 处调用(dispatch_command, dispatch_command_with_args)。

SlashCommand::is_visible246–254 ↗
fn is_visible(self) -> bool

作用:判断某个命令要不要显示在当前用户的命令列表里。有些命令只适合特定系统或调试版本,不该在所有地方出现。

数据流:进去的是一个命令 → 函数查看当前编译环境,比如是不是 Windows、Android、macOS,或者是不是调试版本 → 出来 true 或 false。false 的命令会被藏起来,但不一定代表程序完全不认识它。

调用关系:built_in_slash_commands 会用它过滤命令清单。它调用 cfg! 这个 Rust 编译配置判断工具,根据操作系统和构建模式决定展示与否。

调用图:外部调用 1 个(cfg!)。

built_in_slash_commands258–263 ↗
fn built_in_slash_commands() -> Vec<(&'static str, SlashCommand)>

作用:生成当前环境下应该展示的内置斜杠命令列表。弹出补全菜单时,需要的就是这份清单。

数据流:它不需要外部输入 → 从 SlashCommand 的所有枚举项开始遍历 → 去掉当前环境不可见的命令 → 把每个命令变成“命令文字 + 命令对象”的配对列表 → 返回一个 Vec,也就是一串结果。

调用关系:builtins_for_input 会调用它来拿到可补全的命令。它把 SlashCommand::iter 提供的完整名单、SlashCommand::is_visible 的过滤规则、SlashCommand::command 的名字转换串起来,做成最终菜单原料。

调用图:被 1 处调用(builtins_for_input);外部调用 1 个(iter)。

tests::stop_command_is_canonical_name273–275 ↗
fn stop_command_is_canonical_name()

作用:测试 Stop 命令的正式名字是不是“stop”。这能防止以后有人改别名时不小心把主命令名改坏。

数据流:进去没有业务输入 → 测试取 SlashCommand::Stop.command() 的结果 → 和期望值“stop”比较 → 如果不一样,测试失败并提醒开发者。

调用关系:这是测试代码,在运行测试套件时执行。它用 assert_eq! 做相等检查,间接保护 SlashCommand::command 的行为稳定。

调用图:外部调用 1 个(assert_eq!)。

tests::clean_alias_parses_to_stop_command278–280 ↗
fn clean_alias_parses_to_stop_command()

作用:测试用户输入旧名字或别名“clean”时,程序仍能识别成 Stop 命令。这样老用户或旧脚本不会因为改名而失效。

数据流:进去没有外部输入 → 测试把字符串“clean”解析成 SlashCommand → 期望得到 SlashCommand::Stop → 如果解析不到或解析成别的命令,测试失败。

调用关系:这是测试套件的一部分。它用 assert_eq! 检查 strum 自动生成的字符串解析规则,确保 Stop 的别名兼容性还在。

调用图:外部调用 1 个(assert_eq!)。

tests::pet_alias_parses_to_pets_command283–286 ↗
fn pet_alias_parses_to_pets_command()

作用:测试 Pets 命令的正式显示名是“pets”,同时旧的或单数形式“pet”也能被识别。这样命令既有统一名字,也照顾常见输入。

数据流:进去没有业务输入 → 先检查 SlashCommand::Pets.command() 是否返回“pets” → 再检查字符串“pet”是否能解析成 SlashCommand::Pets → 两项有一项不对,测试就失败。

调用关系:这是命令命名规则的回归测试。它用 assert_eq! 保护 Pets 命令的主名和别名,避免菜单显示和用户输入解析互相打架。

调用图:外部调用 1 个(assert_eq!)。

tests::certain_commands_are_available_during_task289–298 ↗
fn certain_commands_are_available_during_task()

作用:测试一些特定命令在任务运行中仍然被允许使用。它防止开发者误把常用、安全的命令关掉。

数据流:进去没有业务输入 → 测试分别询问 Goal、Ide、Title、Statusline、Raw、App 等命令的可用性 → 期望这些判断返回 true → 如果某个返回 false,测试失败。

调用关系:这是对 SlashCommand::available_during_task、SlashCommand::available_in_side_conversation 和 SlashCommand::supports_inline_args 的保护测试。它用 assert! 检查布尔结果,确保命令分发前的安全规则符合预期。

调用图:外部调用 1 个(assert!)。

tests::auto_review_command_is_approve301–307 ↗
fn auto_review_command_is_approve()

作用:测试 AutoReview 这个内部命令对用户显示和输入时叫“approve”。这能保证自动审查相关流程使用的命令名稳定。

数据流:进去没有业务输入 → 先检查 SlashCommand::AutoReview.command() 是否是“approve” → 再检查字符串“approve”能否解析回 SlashCommand::AutoReview → 结果不符合就让测试失败。

调用关系:这是命令别名和正式名称的测试。它用 assert_eq! 同时保护从命令到文字、从文字到命令这两个方向,避免自动审查批准命令失灵。

调用图:外部调用 1 个(assert_eq!)。

tui/src/bottom_pane/slash_commands.rs源码 ↗
domain_logic用户输入或提交斜杠命令时

可以把这个文件想成“斜杠命令的门卫和菜单编辑”。用户输入“/model”“/clear”这类命令时,系统不能把所有命令都无脑拿出来:有些功能没开,有些命令在侧边对话里不能用,有些服务等级命令只有开关打开后才该出现。这里用 BuiltinCommandFlags 这组开关描述当前环境,再用 builtins_for_input 过滤内置命令。commands_for_input 会把内置命令和模型服务等级命令合成一个列表,给输入框弹窗用。find_builtin_command 和 find_slash_command 则在用户真正提交命令时查找对应命令。一个重要细节是:某些命令即使弹窗里隐藏,精确输入时仍会被识别,这样后面的执行层可以给用户更明确的“当前不可用”提示,而不是假装命令不存在。

函数细节23
SlashCommandItem::command27–32 ↗
fn command(&self) -> &str

作用:取出一个斜杠命令真正显示或匹配用的名字。不管它是系统内置命令,还是服务等级命令,外面都可以用同一个方法拿到名字。

数据流:进去的是一个 SlashCommandItem,也就是“内置命令或服务等级命令”二选一 → 它判断是哪一种:内置命令就问内置命令要名字,服务等级命令就直接取它的 name → 出来的是一个字符串引用,表示命令名,不改动任何数据。

调用关系:它是 SlashCommandItem 这个统一包装上的小工具,供需要按名字显示、搜索或匹配命令的地方使用。文件里的 has_slash_command_prefix 会间接依赖这个能力来做模糊匹配。

SlashCommandItem::supports_inline_args34–39 ↗
fn supports_inline_args(&self) -> bool

作用:判断这个命令后面能不能直接跟参数,比如“/某命令 参数”。服务等级命令不支持这种写法,内置命令则按自己的规则来。

数据流:进去的是一个命令项 → 如果是内置命令,就读取内置命令自己的“是否支持行内参数”设置;如果是服务等级命令,就固定返回 false → 出来的是 true 或 false,不改动状态。

调用关系:它把两类命令的差异藏在一个统一接口后面。调用方不用先判断命令类型,只要问这个方法即可。

SlashCommandItem::available_in_side_conversation41–46 ↗
fn available_in_side_conversation(&self) -> bool

作用:判断这个命令在侧边对话里能不能用。侧边对话是受限制的场景,不是所有主对话命令都适合出现。

数据流:进去的是一个命令项 → 内置命令会使用它自己的可用性规则;服务等级命令固定认为不能在侧边对话中用 → 出来的是是否可用的布尔值。

调用关系:commands_for_input 会用这个规则在侧边对话时再次过滤命令,保证弹窗里不出现不该出现的项。

SlashCommandItem::available_during_task48–53 ↗
fn available_during_task(&self) -> bool

作用:判断当前有任务正在跑的时候,这个命令能不能用。这样可以避免用户在不合适的时机触发会打断或冲突的命令。

数据流:进去的是一个命令项 → 如果是内置命令,就读取内置命令自己的“任务期间是否可用”规则;如果是服务等级命令,就固定返回 false → 出来的是 true 或 false。

调用关系:它会被 reject_slash_command_if_unavailable 调用。也就是说,用户提交命令后,执行前会用它做一道检查,防止任务运行中执行不该执行的命令。

调用图:被 1 处调用(reject_slash_command_if_unavailable)。

builtins_for_input70–82 ↗
fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static str, SlashCommand)>

作用:根据当前功能开关,筛出此刻应该对用户可见、可输入的内置斜杠命令。没有它,关闭的功能或当前场景不支持的命令可能会错误地出现在菜单里。

数据流:进去的是 BuiltinCommandFlags,也就是一组“哪些功能打开、当前是否侧边对话”等开关 → 它先从 built_in_slash_commands 拿到全部内置命令,再逐条检查沙盒提权、协作模式、连接器、插件、用量、目标、人格、侧边对话等限制 → 出来的是过滤后的内置命令列表,原始命令表不被改动。

调用关系:它是本文件的基础过滤器。commands_for_input 用它来生成完整菜单,find_builtin_command 用它来确认某个命令是否经过功能门禁,测试 side_conversation_hides_commands_without_side_flag 也直接检查它的过滤结果。

调用图:调用 1 个内部函数(built_in_slash_commands);被 3 处调用(commands_for_input, find_builtin_command, side_conversation_hides_commands_without_side_flag)。

commands_for_input84–105 ↗
fn commands_for_input(
    flags: BuiltinCommandFlags,
    service_tier_commands: &[ServiceTierCommand],
) -> Vec<SlashCommandItem>

作用:生成输入框弹窗要展示的完整命令列表,包括内置命令,以及在合适位置插入的服务等级命令。它让用户看到的是当前真正相关的一份菜单。

数据流:进去的是功能开关和一组服务等级命令 → 它先调用 builtins_for_input 拿到可见内置命令,再一个个放进结果;遇到 /model 且服务等级命令开关打开时,就把 fast、slow 这类服务等级命令插在 /model 后面 → 最后如果正在侧边对话,还会再过滤一次侧边不可用命令 → 出来的是 SlashCommandItem 列表。

调用关系:它承接 builtins_for_input 的内置命令结果,并把服务等级命令拼进去。命令弹窗创建时会用它,has_slash_command_prefix 也用它判断用户正在输入的前缀像不像一个命令;测试 all_service_tiers_are_exposed_as_commands_after_model 验证服务等级命令确实插在 /model 后面。

调用图:调用 1 个内部函数(builtins_for_input);被 3 处调用(new, has_slash_command_prefix, all_service_tiers_are_exposed_as_commands_after_model);外部调用 3 个(new, iter, Builtin)。

find_builtin_command112–126 ↗
fn find_builtin_command(name: &str, flags: BuiltinCommandFlags) -> Option<SlashCommand>

作用:根据用户输入的名字,找到对应的内置命令。它不只是查标准名字,也允许别名和一个特殊彩蛋:像“goooal”这样多个 o 的写法也能识别成 goal。

数据流:进去的是用户输入的命令名和功能开关 → 它先用 SlashCommand::from_str 尝试按正式名字或别名解析;如果不行,再检查是不是 g + 很多个 o + al 这种 goal 写法 → 解析出候选命令后,再调用 builtins_for_input 做功能门禁确认,但特意放宽“用量命令”和“侧边对话隐藏”的限制 → 出来是找到的内置命令,或 None。

调用关系:find_slash_command 会先调用它,因为内置命令优先于服务等级命令。多个测试直接验证它能识别 debug-config、clear、stop、clean、goooal 等输入,也验证某些隐藏命令仍能被提交阶段识别,好让后续给出更具体的错误信息。

调用图:调用 1 个内部函数(builtins_for_input);被 2 处调用(find_slash_command, debug_command_still_resolves_for_dispatch);外部调用 1 个(from_str)。

find_slash_command128–147 ↗
fn find_slash_command(
    name: &str,
    flags: BuiltinCommandFlags,
    service_tier_commands: &[ServiceTierCommand],
) -> Option<SlashCommandItem>

作用:根据用户提交的名字,找到最终要处理的斜杠命令。它同时支持内置命令和服务等级命令。

数据流:进去的是命令名、功能开关和服务等级命令列表 → 它先调用 find_builtin_command 查内置命令;如果找到了,就包装成 Builtin 返回;如果没找到,再看服务等级命令开关是否打开,打开才按 name 精确查找服务等级命令 → 出来是一个 SlashCommandItem,或者找不到时返回 None。

调用关系:这是提交命令时常用的入口之一。它被 command 和 submit_queued_slash_prompt 调用:用户真正提交斜杠命令后,流程会靠它把文字名字变成系统能执行的命令对象。它内部把内置命令查找交给 find_builtin_command。

调用图:调用 1 个内部函数(find_builtin_command);被 2 处调用(command, submit_queued_slash_prompt);外部调用 1 个(Builtin)。

has_slash_command_prefix149–157 ↗
fn has_slash_command_prefix(
    name: &str,
    flags: BuiltinCommandFlags,
    service_tier_commands: &[ServiceTierCommand],
) -> bool

作用:判断用户当前输入的文字,是否像某个斜杠命令的开头或模糊匹配。这个判断可以用来决定输入框现在是不是正在编辑命令名。

数据流:进去的是用户输入片段、功能开关和服务等级命令列表 → 它调用 commands_for_input 取得当前可用命令菜单,再对每个命令名做 fuzzy_match,也就是“容错匹配,不要求完全一样” → 只要有一个匹配成功就返回 true,否则返回 false。

调用关系:它被 is_editing_command_name 调用,用在用户打字过程中。它不负责执行命令,只负责告诉上层:这段文字看起来是不是命令名,从而让界面决定是否显示命令相关行为。

调用图:调用 1 个内部函数(commands_for_input);被 1 处调用(is_editing_command_name)。

tests::all_enabled_flags165–177 ↗
fn all_enabled_flags() -> BuiltinCommandFlags

作用:给测试准备一份“所有功能都打开”的开关配置。这样每个测试不用重复写一大串布尔值。

数据流:进去没有参数 → 它创建 BuiltinCommandFlags,把协作、连接器、插件、用量、服务等级、目标、人格、沙盒提权都打开,并设置当前不是侧边对话 → 出来是一份完整的测试用开关。

调用关系:这是测试里的公共夹具,也就是测试用的固定准备材料。许多测试会先拿它,再按需要关掉某一个开关,检查过滤规则是否正确。

tests::debug_command_still_resolves_for_dispatch180–183 ↗
fn debug_command_still_resolves_for_dispatch()

作用:确认 debug-config 这个命令在提交执行时仍能被识别。这里重点是“执行阶段能找到它”,不只是弹窗里显示不显示。

数据流:进去没有外部输入 → 它用 all_enabled_flags 生成全开配置,再调用 find_builtin_command 查找 debug-config → 期望出来的是 SlashCommand::DebugConfig。

调用关系:这是 find_builtin_command 的测试用例。它由测试运行器调用,用来防止以后改过滤规则时不小心让 debug-config 无法提交。

调用图:调用 1 个内部函数(find_builtin_command);外部调用 2 个(assert_eq!, all_enabled_flags)。

tests::clear_command_resolves_for_dispatch186–191 ↗
fn clear_command_resolves_for_dispatch()

作用:确认用户输入 clear 时,提交阶段能找到清空相关的内置命令。

数据流:进去没有外部输入 → 它用全开配置查找 clear → 期望得到 SlashCommand::Clear。

调用关系:这是 find_builtin_command 行为的安全网。测试运行器会执行它,保证常用命令 clear 不会因为解析规则变化而失效。

调用图:外部调用 1 个(assert_eq!)。

tests::goal_command_allows_extra_os_for_dispatch194–199 ↗
fn goal_command_allows_extra_os_for_dispatch()

作用:确认 goal 命令支持“goooooal”这种多写几个 o 的趣味输入。用户这样打也会被当成 goal。

数据流:进去没有外部输入 → 它用全开配置查找 goooooooooooal → 期望 find_builtin_command 返回 SlashCommand::Goal。

调用关系:它专门覆盖 find_builtin_command 里的特殊匹配规则,防止这段彩蛋式兼容逻辑被误删。

调用图:外部调用 1 个(assert_eq!)。

tests::stop_command_resolves_for_dispatch202–207 ↗
fn stop_command_resolves_for_dispatch()

作用:确认 stop 这个命令能在提交阶段被识别。

数据流:进去没有外部输入 → 它用全开配置查找 stop → 期望得到 SlashCommand::Stop。

调用关系:这是命令解析的基础测试之一,帮助保证 find_builtin_command 对正式命令名的解析没有坏掉。

调用图:外部调用 1 个(assert_eq!)。

tests::clean_command_alias_resolves_for_dispatch210–215 ↗
fn clean_command_alias_resolves_for_dispatch()

作用:确认 clean 这个别名会被识别成 stop 命令。也就是说,用户打别名也能触发同一个内部命令。

数据流:进去没有外部输入 → 它用全开配置查找 clean → 期望返回 SlashCommand::Stop。

调用关系:它验证 find_builtin_command 会通过 SlashCommand::from_str 接受别名。这样未来改命令名解析时,别名兼容不会被破坏。

调用图:外部调用 1 个(assert_eq!)。

tests::service_tier_commands_are_hidden_when_disabled218–228 ↗
fn service_tier_commands_are_hidden_when_disabled()

作用:确认服务等级命令开关关闭时,像 fast 这样的服务等级命令不会被找到。

数据流:进去没有外部输入 → 它先拿全开配置,再把 service_tier_commands_enabled 改成 false;同时准备一个名为 fast 的服务等级命令 → 调用 find_slash_command 查 fast,期望结果是 None。

调用关系:它测试 find_slash_command 对服务等级命令开关的尊重。这样可以防止功能关闭后,用户仍然能提交相关命令。

调用图:外部调用 3 个(assert_eq!, all_enabled_flags, vec!)。

tests::all_service_tiers_are_exposed_as_commands_after_model231–261 ↗
fn all_service_tiers_are_exposed_as_commands_after_model()

作用:确认服务等级命令会作为命令显示出来,并且紧跟在 /model 后面。这样菜单的顺序更符合用户理解:先选模型,再看到模型相关等级。

数据流:进去没有外部输入 → 它准备 fast 和 slow 两个服务等级命令,再调用 commands_for_input 生成完整列表 → 找到 /model 的位置,取它后面的若干项 → 期望这些项正好是 fast 和 slow 对应的服务等级命令。

调用关系:它直接测试 commands_for_input 的拼装规则。这个测试保证服务等级命令不是随便插到菜单某处,而是稳定地放在 /model 后面。

调用图:调用 1 个内部函数(commands_for_input);外部调用 3 个(assert_eq!, all_enabled_flags, vec!)。

tests::goal_command_is_hidden_when_disabled264–268 ↗
fn goal_command_is_hidden_when_disabled()

作用:确认 goal 功能开关关闭时,goal 命令不会被识别为可用内置命令。

数据流:进去没有外部输入 → 它拿全开配置后关闭 goal_command_enabled → 调用 find_builtin_command 查 goal → 期望结果是 None。

调用关系:它测试 find_builtin_command 和 builtins_for_input 的功能门禁是否生效,避免关闭 goal 功能后仍能使用。

调用图:外部调用 2 个(assert_eq!, all_enabled_flags)。

tests::usage_command_is_hidden_from_input_when_account_token_activity_is_disabled271–280 ↗
fn usage_command_is_hidden_from_input_when_account_token_activity_is_disabled()

作用:确认账号的用量活动功能关闭时,usage 命令不会出现在输入菜单里。

数据流:进去没有外部输入 → 它拿全开配置后关闭 token_activity_command_enabled → 调用 builtins_for_input 取得菜单用内置命令 → 在结果里查找 Usage,期望找不到。

调用关系:它直接验证 builtins_for_input 的菜单过滤行为。这里检查的是“弹窗显示层”隐藏 usage,而不是提交阶段是否识别。

调用图:外部调用 2 个(assert_eq!, all_enabled_flags)。

tests::usage_command_exact_lookup_still_resolves_when_account_token_activity_is_disabled283–290 ↗
fn usage_command_exact_lookup_still_resolves_when_account_token_activity_is_disabled()

作用:确认 usage 虽然在菜单里隐藏,但用户精确输入 usage 时,提交阶段仍能识别它。

数据流:进去没有外部输入 → 它关闭 token_activity_command_enabled,然后调用 find_builtin_command 查 usage → 期望仍返回 SlashCommand::Usage。

调用关系:它验证 find_builtin_command 的一个重要设计:查找命令时故意不完全等同于菜单过滤。这样后续执行层可以告诉用户“这个命令当前不可用的具体原因”。

调用图:外部调用 2 个(assert_eq!, all_enabled_flags)。

tests::side_conversation_hides_commands_without_side_flag293–314 ↗
fn side_conversation_hides_commands_without_side_flag()

作用:确认在侧边对话中,只有明确允许的命令会显示。侧边对话场景更受限,不能把主对话的所有命令都摆出来。

数据流:进去没有外部输入 → 它用全开配置,但把 side_conversation_active 设为 true → 调用 builtins_for_input 生成侧边对话可见命令 → 期望结果只包含 Ide、Copy、Raw、Diff、Mention、Status、Usage 这些允许项。

调用关系:它直接测试 builtins_for_input 对侧边对话的过滤。这个测试保护侧边对话菜单,避免出现会误导用户或无法执行的命令。

调用图:调用 1 个内部函数(builtins_for_input);外部调用 2 个(assert_eq!, all_enabled_flags)。

tests::side_conversation_exact_lookup_still_resolves_hidden_commands_for_dispatch_error317–328 ↗
fn side_conversation_exact_lookup_still_resolves_hidden_commands_for_dispatch_error()

作用:确认侧边对话里即使命令被菜单隐藏,用户精确输入时仍能被识别,方便后面给出“这里不能用”的明确提示。

数据流:进去没有外部输入 → 它构造一个侧边对话激活的全开配置,再查找 review → 期望 find_builtin_command 返回 SlashCommand::Review,而不是 None。

调用关系:它测试 find_builtin_command 与菜单过滤的区别。菜单可以隐藏 review,但提交阶段仍要知道用户输入的是 review,才能交给后续流程生成准确错误。

调用图:外部调用 1 个(assert_eq!)。

tests::side_conversation_exact_lookup_still_resolves_service_tier_commands_for_dispatch_error331–346 ↗
fn side_conversation_exact_lookup_still_resolves_service_tier_commands_for_dispatch_error()

作用:确认侧边对话里,服务等级命令即使不适合显示,精确输入时仍能被 find_slash_command 找到。

数据流:进去没有外部输入 → 它准备一个名为 fast 的服务等级命令,并设置侧边对话激活且功能全开 → 调用 find_slash_command 查 fast → 期望得到对应的 ServiceTier 命令项。

调用关系:它测试 find_slash_command 在侧边对话中的提交阶段行为。和内置命令类似,这保证后续执行层能针对服务等级命令给出具体不可用提示,而不是简单说找不到命令。

调用图:外部调用 2 个(assert_eq!, all_enabled_flags)。

tui/src/bottom_pane/command_popup.rs源码 ↗
domain_logicmain loop,用户输入斜杠命令、移动选择、界面重绘时活跃

这个文件像一个“点菜菜单”。用户在底部输入框里输入“/mo”时,它会把所有可用命令拿出来,按当前功能开关过滤掉不该显示的项,再只留下名字以“mo”开头的命令,比如“/model”。它还会处理滚动、当前选中哪一行、菜单需要多高、以及真正画到终端界面上。这里有两类命令:内置命令和服务档位命令。空输入时,它会隐藏一些别名命令,比如“quit”其实等同于“exit”,避免菜单里重复;但用户真的输入“/qu”时,又会把它显示出来。文件底部的测试用例主要保证这些细节不会被改坏,比如精确匹配优先、顺序稳定、调试命令不出现在普通菜单里。

函数细节29
BuiltinCommandFlags::from56–68 ↗
fn from(value: CommandPopupFlags) -> Self

作用:把命令弹窗自己的开关格式,转换成内置命令列表能看懂的开关格式。这样弹窗和真正的命令系统会按同一套条件决定哪些命令可见。

数据流:进去的是 CommandPopupFlags,里面是一组“某功能是否开启”的布尔值 → 它逐项拷贝并改成 BuiltinCommandFlags 需要的字段名,其中 Windows 沙盒降级状态会变成是否允许提升沙盒的判断 → 出来的是 BuiltinCommandFlags,不改动外部状态。

调用关系:CommandPopup::new 创建菜单时会通过 into 调到它,然后把转换后的开关交给 commands_for_input,让命令来源先筛出当前环境允许出现的命令。

CommandPopup::new72–91 ↗
fn new(
        flags: CommandPopupFlags,
        service_tier_commands: Vec<ServiceTierCommand>,
    ) -> Self

作用:创建一个新的斜杠命令弹窗,并准备好它能显示的命令清单。外部在需要弹出命令提示时会先调用它。

数据流:进去的是一组功能开关和一批服务档位命令 → 它先把开关转换给命令系统,拿到所有当前可用命令,再过滤掉调试命令和 Apps 命令,把内置命令、服务档位命令统一包成 CommandItem → 出来的是 CommandPopup,里面有空的搜索词、命令列表和新的滚动状态。

调用关系:它是这个弹窗的起点。应用代码和大量测试都会先用它造出弹窗;它把“有哪些命令”这件事交给 commands_for_input,把“滚动从零开始”交给 ScrollState::new。

调用图:调用 2 个内部函数(new, commands_for_input);被 17 处调用(command_popup, app_command_popup_snapshot, btw_hidden_in_empty_filter_but_shown_for_prefix, changing_filter_resets_selection_after_scrolling, debug_commands_are_hidden_from_popup, default_command_popup_items_snapshot, filter_includes_init_when_typing_prefix, filtered_commands_keep_presentation_order_for_prefix, model_is_first_suggestion_for_mo, personality_command_hidden_when_disabled (+7 more));外部调用 2 个(new, into)。

CommandPopup::on_composer_text_change97–127 ↗
fn on_composer_text_change(&mut self, text: String)

作用:当输入框文字变化时,更新菜单的搜索词和选中位置。用户每敲一个字,菜单能马上变窄到匹配的命令,主要靠它。

数据流:进去的是当前输入框文本,预期以“/”开头 → 它只看第一行,取第一个斜杠后面的第一个单词作为过滤词;如果过滤词变了,就重置滚动和选中状态;然后根据新的匹配数量修正选中项,保证选中行还在可见范围内 → 出来没有返回值,但会改 CommandPopup 的 command_filter 和 ScrollState。

调用关系:输入框变化时调用它。它会调用 filtered_items 重新计算候选,再让 ScrollState 执行 reset、clamp_selection、ensure_visible,避免旧的滚动位置套到新的结果上。

调用图:调用 4 个内部函数(filtered_items, clamp_selection, ensure_visible, reset)。

CommandPopup::calculate_required_height131–141 ↗
fn calculate_required_height(&self, width: u16) -> u16

作用:计算这个弹窗在给定宽度下需要多高。这样长说明文字换行后,菜单不会画到外面或少占空间。

数据流:进去的是可用宽度 width → 它先拿当前过滤后的命令,转成通用显示行,再交给测量函数按列宽和最大行数计算高度 → 出来的是一个高度数字。

调用关系:界面布局准备画弹窗前会用它估算大小。它把“有哪些行”交给 filtered 和 rows_from_matches,把“每行换行后多高”交给 measure_rows_height_with_col_width_mode。

调用图:调用 3 个内部函数(filtered, rows_from_matches, measure_rows_height_with_col_width_mode)。

CommandPopup::filtered146–194 ↗
fn filtered(&self) -> Vec<(CommandItem, Option<Vec<usize>>)>

作用:根据当前输入的过滤词,找出应该显示的命令,并标出命令名里哪些字符需要高亮。它决定菜单里真正出现什么。

数据流:它读取 self.command_filter 和 self.commands → 如果过滤词为空,就返回完整列表,但跳过 quit、btw 这类别名;如果不为空,就大小写不敏感地找精确匹配和前缀匹配,精确匹配排在前面,并记录匹配字符的位置 → 出来的是命令项加可选高亮下标的列表。

调用关系:这是弹窗筛选的核心。calculate_required_height、filtered_items 和 render_ref 都会用它;后续显示函数再把这些匹配结果变成可画的行。

调用图:被 3 处调用(calculate_required_height, filtered_items, render_ref);外部调用 2 个(new, matches!)。

CommandPopup::filtered_items196–198 ↗
fn filtered_items(&self) -> Vec<CommandItem>

作用:拿到当前过滤后的命令本身,不要高亮信息。上下移动和取当前选中命令时只关心命令,不关心怎么画高亮。

数据流:它读取当前弹窗状态 → 调用 filtered 得到“命令加高亮位置” → 丢掉高亮位置,只返回 CommandItem 列表。

调用关系:move_up、move_down、on_composer_text_change 和 selected_item 都会通过它知道现在有多少候选、选中的是哪一个。它是 filtered 的简化包装。

调用图:调用 1 个内部函数(filtered);被 4 处调用(move_down, move_up, on_composer_text_change, selected_item)。

CommandPopup::rows_from_matches200–222 ↗
fn rows_from_matches(
        &self,
        matches: Vec<(CommandItem, Option<Vec<usize>>)>,
    ) -> Vec<GenericDisplayRow>

作用:把匹配到的命令转换成通用的显示行。渲染和测量高度都需要这种统一格式。

数据流:进去的是一组 CommandItem 和高亮位置 → 它给命令名前面加上“/”,取出说明文字,把高亮位置整体后移一位来避开这个斜杠,然后填进 GenericDisplayRow → 出来的是一组可用于测量和绘制的行。

调用关系:calculate_required_height 和 render_ref 都会调用它。它夹在“命令筛选结果”和“通用列表绘制工具”之间,负责把命令菜单翻译成列表组件认识的格式。

调用图:被 2 处调用(calculate_required_height, render_ref)。

CommandPopup::move_up225–229 ↗
fn move_up(&mut self)

作用:把菜单里的选中光标向上移动一格。到顶时会按滚动状态的规则绕回或保持合理位置。

数据流:它先读取当前过滤后的项目数量 → 让 ScrollState 把选中位置向上移动,并保证新位置在可见窗口里 → 没有返回值,但会改变选中下标和滚动顶部位置。

调用关系:用户按上方向键时会用到它。它不自己计算复杂滚动,而是把实际移动交给 ScrollState::move_up_wrap,再调用 ensure_visible 修正视野。

调用图:调用 3 个内部函数(filtered_items, ensure_visible, move_up_wrap)。

CommandPopup::move_down232–237 ↗
fn move_down(&mut self)

作用:把菜单里的选中光标向下移动一格。用户用键盘浏览命令时靠它前进。

数据流:它先算出当前候选数量 → 让 ScrollState 把选中位置向下移动,并把滚动窗口调整到能看见选中项 → 没有返回值,但会改变弹窗的选择和滚动状态。

调用关系:用户按下方向键时会用到它。它调用 filtered_items 得到列表长度,再把移动和可见性处理交给 ScrollState。

调用图:调用 3 个内部函数(filtered_items, ensure_visible, move_down_wrap)。

CommandPopup::selected_item240–245 ↗
fn selected_item(&self) -> Option<CommandItem>

作用:返回当前选中的命令。用户按回车确认时,外部可以用它知道该执行哪个斜杠命令。

数据流:它读取当前过滤后的命令列表和 ScrollState 里的 selected_idx → 如果有合法下标,就克隆对应 CommandItem 返回;如果没有可选项或下标不存在,就返回 None → 不改动状态。

调用关系:确认选择、测试精确匹配时会用到它。它依赖 filtered_items 保证拿到的是最新过滤结果。

调用图:调用 1 个内部函数(filtered_items)。

CommandItem::command249–254 ↗
fn command(&self) -> &str

作用:取出一个命令真正显示和匹配用的名字,不带前面的斜杠。无论是内置命令还是服务档位命令,外部都能用同一个方法拿名字。

数据流:进去的是一个 CommandItem 引用 → 如果是内置命令,就读取 SlashCommand 的命令名;如果是服务档位,就读取 ServiceTierCommand 的 name 字段 → 出来的是字符串引用,不改动数据。

调用关系:筛选、显示行生成和测试都会间接或直接用它。它把两种命令来源的差异藏起来,让上层不用分别处理。

CommandItem::description256–261 ↗
fn description(&self) -> &str

作用:取出命令的说明文字,用来显示在菜单右侧或下一列。用户看它就知道这个命令会做什么。

数据流:进去的是一个 CommandItem 引用 → 内置命令读取内置说明,服务档位命令读取 catalog 里带来的 description 字段 → 出来的是说明文字引用。

调用关系:rows_from_matches 和部分快照测试会用它准备显示内容。它和 CommandItem::command 一起组成每一行的“名字加解释”。

CommandPopup::render_ref265–278 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

作用:把命令弹窗真正画到终端界面的缓冲区里。前面的函数决定显示什么,它负责把内容落到屏幕区域中。

数据流:进去的是要绘制的矩形区域 area 和可写的 Buffer → 它先筛选命令并转成显示行,再给左边留出一点内边距,最后调用通用列表渲染函数画出行、选中状态和“no matches”提示 → 没有返回值,但会修改 Buffer,让终端之后能显示这些内容。

调用关系:ratatui 界面框架在绘制控件时会调用它。它把 filtered、rows_from_matches 的结果交给 render_rows_with_col_width_mode,自己只负责命令弹窗到通用列表渲染器的衔接。

调用图:调用 4 个内部函数(filtered, rows_from_matches, render_rows_with_col_width_mode, tlbr);外部调用 1 个(inset)。

tests::filter_includes_init_when_typing_prefix287–304 ↗
fn filter_includes_init_when_typing_prefix()

作用:测试用户输入“/in”时,菜单里会出现“init”命令。它防止前缀搜索把应有命令漏掉。

数据流:测试先创建默认弹窗 → 模拟输入框变成“/in” → 读取过滤后的项目并检查其中是否有 init → 结果是断言通过或测试失败,不影响正式运行。

调用关系:这是针对 CommandPopup::new、on_composer_text_change 和 filtered_items 的保护性测试,确保它们配合后能做前缀匹配。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert!, default)。

tests::selecting_init_by_exact_match307–321 ↗
fn selecting_init_by_exact_match()

作用:测试输入完整的“/init”时,默认选中的就是 init。它保证精确匹配不会被别的前缀结果抢到前面。

数据流:测试创建弹窗 → 输入“/init” → 调 selected_item 取当前选择 → 如果选中的是内置 init 就通过,否则报错。

调用关系:它验证 filtered 的“精确匹配优先”和 selected_item 的取值行为,防止回车执行错命令。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, panic!, default)。

tests::model_is_first_suggestion_for_mo324–335 ↗
fn model_is_first_suggestion_for_mo()

作用:测试输入“/mo”时,第一个建议是 model。它保护常用命令的展示顺序。

数据流:测试创建弹窗 → 模拟输入“/mo” → 拿过滤结果第一项 → 检查它是不是 model → 不符合就失败。

调用关系:它关注 filtered 保留命令展示顺序的行为,也间接保证 commands_for_input 给出的顺序没有被搜索逻辑打乱。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, panic!, default)。

tests::service_tier_command_uses_catalog_name_and_description338–368 ↗
fn service_tier_command_uses_catalog_name_and_description()

作用:测试服务档位命令会使用外部目录给的名字和说明。这样动态下发的命令不会被当成内置命令错误显示。

数据流:测试创建一个启用服务档位命令的弹窗,并传入名为 fast 的服务档位 → 输入“/fa” → 检查选中项是这个服务档位,并检查显示行里的说明文字正确 → 断言决定测试是否通过。

调用关系:它验证 CommandPopup::new 对 ServiceTierCommand 的接收、filtered 的匹配、selected_item 的返回,以及 rows_from_matches 对说明文字的处理。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, panic!, default, vec!)。

tests::filtered_commands_keep_presentation_order_for_prefix371–392 ↗
fn filtered_commands_keep_presentation_order_for_prefix()

作用:测试前缀搜索不会随意重排结果。用户输入“/m”时,应按既定展示顺序看到 model、memories、mention、mcp。

数据流:测试创建弹窗 → 输入“/m” → 把过滤后的命令名收集成字符串列表 → 和预期顺序比较 → 不一致就失败。

调用关系:它主要约束 filtered:匹配可以筛少,但不能把原本的展示顺序弄乱。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, default)。

tests::app_command_popup_snapshot396–411 ↗
fn app_command_popup_snapshot()

作用:用快照测试检查“/app”菜单画出来的样子。快照测试就是把界面输出存一份标准答案,以后变化时自动提醒。

数据流:测试创建弹窗并输入“/app” → 计算高度、创建空缓冲区、调用 render_ref 绘制 → 把缓冲区文字和保存的快照比较 → 画面变了就失败。

调用关系:它覆盖 calculate_required_height 和 render_ref 的配合,特别是在 macOS 或 Windows 上检查 app 相关显示是否稳定。

调用图:调用 1 个内部函数(new);外部调用 5 个(empty, new, new, assert_snapshot!, default)。

tests::default_command_popup_items_snapshot415–431 ↗
fn default_command_popup_items_snapshot()

作用:用快照记录默认输入“/”时菜单里有哪些命令和说明。它防止默认列表被无意改动。

数据流:测试创建弹窗 → 输入“/” → 把过滤后的每个命令整理成“/命令 - 说明”文本 → 和快照比较 → 不一致时测试提示需要确认变化。

调用关系:它检查 filtered_items、CommandItem::command 和 CommandItem::description 在默认菜单里的组合效果。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_snapshot!, default)。

tests::prefix_filter_limits_matches_for_ac434–450 ↗
fn prefix_filter_limits_matches_for_ac()

作用:测试搜索只做前缀匹配,不做中间包含匹配。输入“/ac”时,不应该因为 compact 里面有 ac 就显示它。

数据流:测试创建弹窗 → 输入“/ac” → 收集命令名 → 断言列表里没有 compact → 如果出现就失败。

调用关系:它直接保护 filtered 的匹配规则,让菜单行为更可预测:用户输入的是开头,不是全文模糊搜索。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert!, default)。

tests::changing_filter_resets_selection_after_scrolling453–482 ↗
fn changing_filter_resets_selection_after_scrolling()

作用:测试用户先滚动菜单,再换搜索词时,选择和滚动会回到合理位置。没有这个行为,菜单可能停在看不见或错位的位置。

数据流:测试创建弹窗并显示全列表 → 连续向下移动到发生滚动 → 再输入“/st” → 检查选中项变成 status、滚动顶部归零,并画出界面与快照比较 → 任一条件不对就失败。

调用关系:它验证 on_composer_text_change 在过滤词变化时调用 reset、clamp_selection、ensure_visible 的效果,也覆盖 move_down、calculate_required_height 和 render_ref。

调用图:调用 1 个内部函数(new);外部调用 7 个(empty, new, new, assert!, assert_eq!, assert_snapshot!, default)。

tests::quit_hidden_in_empty_filter_but_shown_for_prefix485–494 ↗
fn quit_hidden_in_empty_filter_but_shown_for_prefix()

作用:测试 quit 这个别名在默认列表里隐藏,但用户明确输入“/qu”时会出现。这样既避免重复,又不阻止熟悉别名的用户使用。

数据流:测试创建弹窗 → 输入“/”并确认列表没有 quit → 再输入“/qu”并确认列表有 quit → 断言决定测试是否通过。

调用关系:它约束 filtered 对 ALIAS_COMMANDS 的特殊处理:只在空过滤词时隐藏别名,有前缀搜索时仍允许匹配。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert!, default)。

tests::btw_hidden_in_empty_filter_but_shown_for_prefix497–506 ↗
fn btw_hidden_in_empty_filter_but_shown_for_prefix()

作用:测试 btw 这个别名和 quit 一样:默认隐藏,明确搜索时显示。它保证别名隐藏规则不只对一个命令生效。

数据流:测试创建弹窗 → 输入“/”检查没有 btw → 输入“/bt”检查有 btw → 不符合就失败。

调用关系:它继续保护 filtered 对别名命令的逻辑,特别是 ALIAS_COMMANDS 里 btw 这一项。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert!, default)。

tests::plan_command_hidden_when_collaboration_modes_disabled509–525 ↗
fn plan_command_hidden_when_collaboration_modes_disabled()

作用:测试协作模式没开启时,plan 命令不会出现在菜单里。这样用户不会看到当前环境不能用的功能。

数据流:测试用默认开关创建弹窗 → 输入“/” → 收集所有命令名 → 断言里面没有 plan → 如果出现就失败。

调用关系:它验证 CommandPopup::new 把开关传给 commands_for_input 后,命令可见性会正确影响弹窗。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert!, default)。

tests::plan_command_visible_when_collaboration_modes_enabled528–552 ↗
fn plan_command_visible_when_collaboration_modes_enabled()

作用:测试协作模式开启时,plan 命令可以被搜索并选中。它和隐藏测试配成一对,确认开关两种状态都正确。

数据流:测试用 collaboration_modes_enabled 为 true 的开关创建弹窗 → 输入“/plan” → 调 selected_item → 如果选中内置 plan 就通过,否则失败。

调用关系:它验证 BuiltinCommandFlags::from、CommandPopup::new、commands_for_input 和 selected_item 的整条路径。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, panic!)。

tests::personality_command_hidden_when_disabled555–584 ↗
fn personality_command_hidden_when_disabled()

作用:测试 personality 功能关闭时,personality 命令不会被搜出来。它防止实验性或受控功能提前暴露给用户。

数据流:测试用 personality_command_enabled 为 false 的开关创建弹窗 → 输入“/pers” → 收集匹配命令名 → 断言没有 personality → 出现则失败。

调用关系:它检查功能开关经过 BuiltinCommandFlags::from 和 CommandPopup::new 后,会影响 filtered_items 能看到的命令。

调用图:调用 1 个内部函数(new);外部调用 2 个(new, assert!)。

tests::personality_command_visible_when_enabled587–611 ↗
fn personality_command_visible_when_enabled()

作用:测试 personality 功能开启时,输入完整命令能选中 personality。它保证开关打开后功能入口确实出现。

数据流:测试用 personality_command_enabled 为 true 的开关创建弹窗 → 输入“/personality” → 读取 selected_item → 确认是内置 personality 命令 → 否则失败。

调用关系:它和隐藏测试一起覆盖 personality 命令的可见性,并验证精确匹配时 selected_item 返回正确命令。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, panic!)。

tests::debug_commands_are_hidden_from_popup614–629 ↗
fn debug_commands_are_hidden_from_popup()

作用:测试普通命令菜单里不会出现 debug 开头的调试命令。调试命令通常给开发者用,不该打扰普通用户。

数据流:测试创建默认弹窗 → 读取当前过滤项目并收集命令名 → 断言没有任何名字以 debug 开头 → 如果有就失败。

调用关系:它保护 CommandPopup::new 里的过滤规则:commands_for_input 可能提供调试命令,但弹窗构建时要把它们挡在普通菜单外。

调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert!, default)。

tui/src/bottom_pane/chat_composer/slash_input.rs源码 ↗
domain_logic用户在聊天输入框输入、补全、提交命令时活跃

这个文件专门处理聊天框里的斜杠命令。斜杠命令就是以“/”开头的快捷指令,像在聊天软件里输入“/help”一样。它先判断当前输入是不是命令、命令名是不是存在、光标是不是正停在命令名里;然后决定要不要显示命令候选弹窗。用户按上下键时,它移动候选;按 Tab、Enter 或 “/” 时,它会补全命令,或者直接执行命令。它还特别照顾一种情况:用户输入“/re查看差异”时,补全成“/review 查看差异”,尽量保住后面的草稿文字。文件里也处理“文字元素”的范围标记,保证命令在输入框里像一个完整的小块一样被识别。底部的测试则检查这些容易出错的补全场景。

函数细节26
SlashInput::new54–66 ↗
fn new(
        enabled: bool,
        is_bash_mode: bool,
        command_flags: BuiltinCommandFlags,
        service_tier_commands: &'a [ServiceTierCommand],
    ) -> Self

作用:创建一个 SlashInput,用来记住斜杠命令当前是否启用、是否处在 bash 模式,以及哪些命令可以用。它像给输入框配一张“命令规则表”。

数据流:进去的是开关状态、bash 模式状态、内置命令功能开关、服务档位命令列表 → 它把这些值存进结构体 → 出来一个之后可用于解析和校验斜杠命令的 SlashInput。

调用关系:它由 slash_input 创建,后面的校验、补全、弹窗过滤都会依赖这个对象里保存的规则。

调用图:被 1 处调用(slash_input)。

SlashInput::validate_submission68–87 ↗
fn validate_submission(
        &self,
        text: &str,
        input_starts_with_space: bool,
    ) -> SubmissionValidation

作用:在用户准备提交输入时,检查开头的斜杠命令是不是合法。这样可以避免用户把拼错的“/命令”误当成有效命令提交。

数据流:进去的是整段输入文字,以及输入是否以空格开头 → 它先尝试拆出命令名,再看斜杠命令是否启用、是否应该跳过校验、命令是否存在 → 出来的是“有效”或“未知命令”,不会改动输入框内容。

调用关系:它调用 parse_slash_name 拆命令名,再通过 SlashInput::command 查命令;如果找不到,就返回 UnknownCommand,提醒上层不要把它当正常命令放行。

调用图:调用 2 个内部函数(command, parse_slash_name);外部调用 1 个(UnknownCommand)。

SlashInput::bare_command89–105 ↗
fn bare_command(&self, text: &str) -> Option<SlashCommandItem>

作用:判断输入是不是一个“只有命令、没有参数”的斜杠命令。比如“/model”可能是裸命令,而“/review 这段代码”就不是。

数据流:进去的是输入文字 → 它只看第一行,拆出命令名和后面的内容,并排除禁用状态、bash 模式、有参数等情况 → 出来的是匹配到的命令,或者什么都没有。

调用关系:它用 parse_slash_name 读出命令,再交给 SlashInput::command 查找真实命令;上层可据此决定是否直接按命令处理。

调用图:调用 2 个内部函数(command, parse_slash_name)。

SlashInput::inline_command107–123 ↗
fn inline_command(&self, text: &'text str) -> Option<InlineCommand<'text>>

作用:识别“命令加参数”的输入,例如“/review 看看这个 diff”。它用于把命令名和后面的参数分开。

数据流:进去的是整段文字 → 它检查功能是否开启、是否 bash 模式、是否以空格开头,然后拆出命令名、参数和参数起始位置 → 如果命令存在且支持行内参数,就出来一个 InlineCommand,里面带着命令和参数信息。

调用关系:它调用 parse_slash_name 拆文本,再用 SlashInput::command 找命令;后续提交逻辑可以根据 InlineCommand 把参数传给对应命令。

调用图:调用 2 个内部函数(command, parse_slash_name)。

SlashInput::should_parse_on_dequeue125–127 ↗
fn should_parse_on_dequeue(&self, text: &str) -> bool

作用:判断一段排队中的输入,在真正拿出来处理时,是否还需要按斜杠命令重新解析。它主要防止运行中排队的命令被当成普通文字。

数据流:进去的是准备处理的文字 → 它看斜杠命令是否启用、文字是否不是空格开头、去掉前后空白后是否以“/”开头 → 出来一个是或否。

调用关系:它为排队输入的后续处理提供一个简单判断,让上层知道是否要走命令解析流程。

SlashInput::command_element_range129–155 ↗
fn command_element_range(
        &self,
        first_line: &str,
        cursor: usize,
    ) -> Option<Range<usize>>

作用:找出第一行里真正的斜杠命令名范围,用来把它标记成一个特殊文本块。这样界面知道“/review”是命令,不只是几个普通字符。

数据流:进去的是第一行文字和光标位置 → 它拆出命令名,排除 bash 模式、嵌套斜杠、光标仍在编辑命令名、命令后没有空白等情况 → 出来的是命令在文字里的字节范围,或者没有范围。

调用关系:它调用 parse_slash_name 和 SlashInput::command;ChatComposer::sync_slash_command_elements 会用它来添加或删除输入框里的命令标记。

调用图:调用 2 个内部函数(command, parse_slash_name)。

SlashInput::is_editing_command_name157–169 ↗
fn is_editing_command_name(&self, first_line: &str, cursor: usize) -> bool

作用:判断用户当前是不是正在编辑斜杠命令的名字。这个判断决定候选弹窗该不该出现、该按什么内容过滤。

数据流:进去的是第一行文字和光标位置 → 它先找光标下的命令片段,再检查斜杠功能是否开启,以及这个片段是不是某个命令的前缀 → 出来一个是或否。

调用关系:它把定位工作交给 command_under_cursor,把“是否像某个命令开头”的判断交给 has_slash_command_prefix;上层据此决定是否显示命令弹窗。

调用图:调用 2 个内部函数(command_under_cursor, has_slash_command_prefix)。

SlashInput::command_popup171–188 ↗
fn command_popup(&self, filter_text: &str) -> CommandPopup

作用:根据当前输入内容创建斜杠命令候选弹窗。它会把当前可用的命令开关带进去,让弹窗只显示此时能用的命令。

数据流:进去的是过滤文字 → 它用命令功能开关和服务档位命令列表创建 CommandPopup,再把过滤文字交给弹窗刷新候选 → 出来一个已经按输入过滤好的弹窗对象。

调用关系:它调用 CommandPopup::new,并把 service_tier_commands 复制成弹窗可持有的列表;输入框同步弹窗时会用到它。

调用图:调用 1 个内部函数(new);外部调用 1 个(to_vec)。

SlashInput::command190–192 ↗
fn command(&self, name: &str) -> Option<SlashCommandItem>

作用:按名字查找一个斜杠命令。它是这个文件里多个解析函数共用的“查命令字典”的小入口。

数据流:进去的是命令名 → 它带着当前命令开关和服务档位命令列表去查找 → 出来的是匹配到的 SlashCommandItem,或者没有匹配。

调用关系:它把实际查找交给 find_slash_command;SlashInput::validate_submission、bare_command、inline_command、command_element_range 都靠它确认命令是否真的存在。

调用图:调用 1 个内部函数(find_slash_command);被 4 处调用(bare_command, command_element_range, inline_command, validate_submission)。

queued_input_action195–206 ↗
fn queued_input_action(
    prepared_text: &str,
    defer_slash_validation: bool,
) -> QueuedInputAction

作用:给一段准备排队的输入贴标签:之后是按斜杠命令处理、跑 shell 命令,还是当普通文字。这里的 shell 命令指以“!”开头、让系统执行的命令。

数据流:进去的是准备好的文字,以及是否延后校验斜杠命令的开关 → 它看文字开头是“/”还是“!” → 出来 QueuedInputAction,告诉后面该怎么处理。

调用关系:它被 handle_submission_with_time 使用;当任务还在运行、输入需要先排队时,这个函数决定队列里的项目之后走哪条路。

调用图:被 1 处调用(handle_submission_with_time)。

ChatComposer::handle_key_event_with_slash_popup210–380 ↗
fn handle_key_event_with_slash_popup(
        &mut self,
        key_event: KeyEvent,
    ) -> (InputResult, bool)

作用:当斜杠命令弹窗已经打开时,专门处理用户按键。它决定上下键选哪个命令、Tab 怎么补全、Enter 是执行还是继续编辑。

数据流:进去的是一个键盘事件 → 它先处理快捷键和 Esc 提示,再根据按键移动弹窗选择、关闭弹窗、补全命令、提交命令或回退到普通输入处理 → 出来 InputResult 和一个“事件是否已处理”的标记,同时可能改动输入框文字、光标、弹窗状态和 bash 模式。

调用关系:它是弹窗打开时的核心分发点;会调用 command_popup_filter_text 更新过滤内容,调用 selected_command_completion 做普通补全,调用 complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args 做保留草稿的补全,也会在需要时交给 handle_submission 或 handle_key_event_without_popup。

调用图:调用 6 个内部函数(complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args, command_popup_filter_text, selected_command_completion, selected_command_dispatches_immediately_on_tab, esc_hint_mode, reset_mode_after_activity);外部调用 3 个(Command, ServiceTierCommand, unreachable!)。

ChatComposer::complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args382–445 ↗
fn complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args(
        &mut self,
        selected_cmd: &CommandItem,
    ) -> bool

作用:把当前选中的命令补全进输入框,同时尽量保留光标后面的草稿,当成命令参数。比如把“/re查看差异”变成“/review 查看差异”。

数据流:进去的是弹窗里选中的命令 → 它确认这是支持行内参数的内置命令,计算要替换的命令片段和要保留的尾巴,必要时去掉被切开的文本元素标记,再替换输入框开头并移动光标到末尾 → 出来 true 或 false,同时可能修改输入文字、文本元素、光标和 bash 模式。

调用关系:它只由 ChatComposer::handle_key_event_with_slash_popup 调用;当用户按 Tab、Enter 或“/”接受候选时,这个函数负责最细腻的补全场景。

调用图:被 1 处调用(handle_key_event_with_slash_popup);外部调用 1 个(format!)。

ChatComposer::sync_slash_command_elements448–485 ↗
fn sync_slash_command_elements(&mut self)

作用:同步输入框里的斜杠命令标记,保证当前第一行的命令被正确标成特殊元素,旧的错误标记被清掉。

数据流:进去的是 ChatComposer 当前状态 → 它读取输入框文字、第一行、光标和已有文本元素,算出现在应该标记的命令范围,删除不匹配的旧范围,再补上缺失的新范围 → 没有单独返回值,但会修改输入框的文本元素标记。

调用关系:它依赖 self.slash_input().command_element_range 判断应该标哪里;每当输入变化需要刷新界面状态时,它让命令标记和真实文字保持一致。

调用图:外部调用 1 个(new)。

selected_command_dispatches_immediately_on_tab488–490 ↗
fn selected_command_dispatches_immediately_on_tab(command: &CommandItem) -> bool

作用:判断某个命令在按 Tab 时是否应该立刻执行,而不是只补全文字。目前这个特殊行为只给 Skills 命令。

数据流:进去的是弹窗选中的命令项 → 它检查它是不是内置的 Skills 命令 → 出来一个是或否。

调用关系:它被 ChatComposer::handle_key_event_with_slash_popup 调用;Tab 键处理时先问它,若返回 true,就直接产生命令结果。

调用图:被 1 处调用(handle_key_event_with_slash_popup);外部调用 1 个(matches!)。

selected_command_completion492–499 ↗
fn selected_command_completion(
    first_line: &str,
    command: &CommandItem,
) -> Option<String>

作用:生成把当前第一行补全成所选命令的文字,比如把“/mo”补成“/model ”。如果已经是这个命令开头,就不重复补。

数据流:进去的是第一行文字和选中的命令 → 它拼出完整的“/命令名 ”,再判断当前行是否已经以它开头 → 出来补全后的字符串,或者没有需要补全的内容。

调用关系:它被 ChatComposer::handle_key_event_with_slash_popup 使用;当更复杂的“保留草稿补全”不适用时,就用这个普通补全。

调用图:被 1 处调用(handle_key_event_with_slash_popup);外部调用 1 个(format!)。

prepared_args501–504 ↗
fn prepared_args(prepared_text: &str) -> Option<(&str, usize)>

作用:从已经准备好的斜杠命令文本里取出参数部分。它让后续逻辑不用再自己猜命令名后面从哪里开始。

数据流:进去的是准备提交的完整文字 → 它用 parse_slash_name 拆出命令名后面的 rest 和 rest 的起始位置 → 出来参数文本和它在原文里的偏移量,或者没有结果。

调用关系:它被 prepare_inline_args_submission 调用;后者会用这里得到的参数内容继续准备带参数的命令提交。

调用图:调用 1 个内部函数(parse_slash_name);被 1 处调用(prepare_inline_args_submission)。

args_elements509–532 ↗
fn args_elements(
    rest: &str,
    rest_offset: usize,
    text_elements: &[TextElement],
) -> Vec<TextElement>

作用:把整段输入里的文本元素范围,换算成“只看命令参数”时的范围。文本元素可以理解为输入框里被特殊标记的一段文字,比如一个引用块或占位块。

数据流:进去的是参数文本、参数在全文中的起点、全文的文本元素列表 → 它丢掉完全在参数之前的元素,把剩下元素的起止位置减去参数偏移,并裁剪到参数长度内 → 出来一组新的 TextElement,范围都相对于参数文本。

调用关系:它被 prepare_inline_args_submission 和 try_dispatch_slash_command_with_args 使用;这样命令收到参数时,参数里的特殊标记仍然对得上位置。

调用图:被 2 处调用(prepare_inline_args_submission, try_dispatch_slash_command_with_args);外部调用 3 个(new, is_empty, iter)。

command_popup_filter_text534–537 ↗
fn command_popup_filter_text(first_line: &str, cursor: usize) -> Option<String>

作用:根据光标所在位置,生成弹窗过滤用的“/命令片段”。例如光标在“/rev”里,就返回“/rev”。

数据流:进去的是第一行文字和光标位置 → 它先确认光标确实在斜杠命令名里,再把命令片段前面补上“/” → 出来过滤字符串,或者没有结果。

调用关系:它被 ChatComposer::handle_key_event_with_slash_popup 调用,用来在 Tab 或“/”接受候选前,让弹窗选择和当前输入保持同步;内部定位交给 command_under_cursor。

调用图:调用 1 个内部函数(command_under_cursor);被 1 处调用(handle_key_event_with_slash_popup);外部调用 1 个(format!)。

command_under_cursor541–568 ↗
fn command_under_cursor(first_line: &str, cursor: usize) -> Option<(&str, &str)>

作用:找出光标当前是否位于第一行的斜杠命令名中,并切出光标前的命令片段和光标后的剩余文字。

数据流:进去的是第一行文字和光标字节位置 → 它检查文字是否以“/”开头、光标是否落在合法字符边界内,再找到命令名结束位置 → 出来命令片段和后续文字,或者没有结果。

调用关系:它是一个底层定位工具;SlashInput::is_editing_command_name 用它判断是否正在编辑命令,command_popup_filter_text 用它生成弹窗过滤文字。

调用图:被 2 处调用(is_editing_command_name, command_popup_filter_text)。

tests::test_composer578–587 ↗
fn test_composer() -> ChatComposer

作用:创建一个用于测试的 ChatComposer。它给测试准备一个最小可用的聊天输入框环境。

数据流:进去没有业务输入 → 它创建一个应用事件通道,再用固定参数构造 ChatComposer → 出来一个可在测试里模拟输入的 composer。

调用关系:它被测试辅助函数间接使用;内部调用 ChatComposer::new 和 AppEventSender::new,避免每个测试重复搭环境。

调用图:调用 2 个内部函数(new, new)。

tests::press589–593 ↗
fn press(composer: &mut ChatComposer, code: KeyCode) -> InputResult

作用:在测试里模拟用户按下一个键,并取回输入框处理后的结果。这样测试写起来像“按一下 Tab 看发生什么”。

数据流:进去的是可变的 ChatComposer 和一个按键代码 → 它构造 KeyEvent,交给 composer.handle_key_event → 出来 InputResult,composer 自身也可能被改动。

调用关系:多个测试用它触发 Tab、Enter 等行为;它把真实按键处理入口包了一层,方便断言结果。

调用图:调用 1 个内部函数(handle_key_event);外部调用 1 个(new)。

tests::composer_with_text_at_cursor595–601 ↗
fn composer_with_text_at_cursor(text: &str, cursor: usize) -> ChatComposer

作用:创建一个带指定文字和指定光标位置的测试输入框。它让测试能精确复现用户正在编辑某个位置的场景。

数据流:进去的是文本和光标位置 → 它先拿到测试用 composer,设置输入框文字,移动光标,再同步弹窗状态 → 出来准备好的 ChatComposer。

调用关系:测试用它构造“/review 光标在 /re 后面”这类场景;它依赖 tests::test_composer 提供基础对象。

调用图:外部调用 1 个(test_composer)。

tests::composer_with_draft_tail603–605 ↗
fn composer_with_draft_tail(prefix: &str, draft: &str) -> ChatComposer

作用:创建一个“命令前缀 + 草稿尾巴”的测试输入框,并把光标放在前缀后面。它专门用来测试补全时能不能保住后面的草稿。

数据流:进去的是命令前缀和草稿文字 → 它把两者拼成完整输入,把光标设到前缀末尾 → 出来对应的 ChatComposer。

调用关系:补全相关测试会用它快速搭场景;它把实际创建工作交给 tests::composer_with_text_at_cursor。

调用图:外部调用 2 个(format!, composer_with_text_at_cursor)。

tests::slash_completion_preserves_existing_draft_tail_for_inline_arg_commands608–623 ↗
fn slash_completion_preserves_existing_draft_tail_for_inline_arg_commands()

作用:测试支持行内参数的命令在补全时会保留草稿尾巴。例子是把“/re”补成“/review”,同时留下“view the diff”。

数据流:进去没有外部输入 → 它创建两次带草稿尾巴的 composer,分别模拟按 Tab 和 Enter → 断言输出结果、输入框文字和光标位置都符合预期。

调用关系:它通过 tests::composer_with_draft_tail 和 tests::press 触发真实输入处理,验证 ChatComposer::complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args 的关键行为。

调用图:外部调用 2 个(assert_eq!, composer_with_draft_tail)。

tests::slash_completion_does_not_preserve_existing_draft_tail_for_other_commands626–635 ↗
fn slash_completion_does_not_preserve_existing_draft_tail_for_other_commands()

作用:测试不支持行内参数的命令不会把后面的草稿当成参数保留下来。这样可以避免把普通补全误变成带参数命令。

数据流:进去没有外部输入 → 它创建一个“/mo + 草稿”的 composer,模拟按 Tab → 断言最终只补成“/model ”,光标也在末尾。

调用关系:它使用 tests::composer_with_draft_tail 和 tests::press,验证普通命令会走 selected_command_completion 这类简单补全,而不是保留草稿的特殊路径。

调用图:外部调用 2 个(assert_eq!, composer_with_draft_tail)。

tests::slash_completion_does_not_turn_command_suffix_into_args638–649 ↗
fn slash_completion_does_not_turn_command_suffix_into_args()

作用:测试补全命令时,不会把命令名本身剩下的字母误当成参数。比如光标在“/review”的“/re”后面时,后面的“view”不该变成参数。

数据流:进去没有外部输入 → 它构造光标位于命令名中间的 composer,分别模拟 Tab 和 Enter → 断言 Tab 只是补成“/review ”,Enter 则执行 Review 命令并清空输入。

调用关系:它使用 tests::composer_with_text_at_cursor 和 tests::press,专门保护 command_under_cursor 与补全逻辑在“光标在命令名中间”这个边界场景下不出错。

调用图:外部调用 3 个(assert!, assert_eq!, composer_with_text_at_cursor)。

tui/src/bottom_pane/file_search_popup.rs源码 ↗
domain_logic用户输入文件搜索、搜索结果返回、终端界面刷新时活跃

这个文件像一个小型的“搜索结果菜单”。它记住三类信息:用户最新输入了什么、当前屏幕上显示的是哪次搜索结果、以及光标选中了哪一行。这样做是为了防止一个常见问题:用户打字很快,旧搜索结果晚回来,如果直接显示,就会把新输入对应的结果冲掉。所以它只接受和当前输入一致的结果。它还会把结果数量限制在弹窗最大行数内,避免列表把终端撑满。上下移动时,它借用 ScrollState 这套滚动状态,保证选中的行总在可见范围里。真正画到屏幕上时,它把文件搜索结果转换成通用的显示行,再交给 render_rows 统一渲染。文件末尾的测试确认:结果再多,也只保留第一页能显示的数量。

函数细节11
FileSearchPopup::new32–40 ↗
fn new() -> Self

作用:创建一个全新的文件搜索弹窗状态。刚创建时还没有结果,并且默认认为正在等待搜索结果。

数据流:进去没有参数 → 它准备空的显示查询、空的待搜索查询、空结果列表,并创建一份新的滚动/选择状态 → 出来一个可以被界面使用的 FileSearchPopup。

调用关系:当界面同步文件搜索弹窗时会用它来起步,测试里也用它造一个干净的弹窗。它内部会新建字符串、结果列表和 ScrollState,后面的 set_query、set_matches、render_ref 都是在这个状态上继续工作。

调用图:调用 1 个内部函数(new);被 2 处调用(sync_file_search_popup, set_matches_keeps_only_the_first_page_of_results);外部调用 2 个(new, new)。

FileSearchPopup::set_query43–52 ↗
fn set_query(&mut self, query: &str)

作用:记录用户刚输入的新搜索词,并把弹窗标记成“正在等结果”。如果输入没变,它什么也不做,避免重复刷新。

数据流:进去一个查询字符串 → 它先和当前 pending_query 比较;如果不同,就清空旧的待搜索词,写入新的词,并把 waiting 设为 true → 出来时弹窗知道自己在等这次新查询的结果。

调用关系:它通常发生在用户继续打字之后。它不会自己搜索文件,只是更新弹窗状态;等搜索系统把结果送回来后,FileSearchPopup::set_matches 会根据这里保存的 pending_query 判断结果是不是还新鲜。

FileSearchPopup::set_empty_prompt56–63 ↗
fn set_empty_prompt(&mut self)

作用:把弹窗切到“空提示”状态,用在用户只输入了 @、还没有真正搜索词的时候。此时不显示结果,而是清空旧状态,避免误导用户。

数据流:进去没有参数 → 它清空显示查询和待搜索查询,把 waiting 设为 false,清掉所有匹配结果,并重置选择/滚动状态 → 出来时弹窗回到一个干净的提示状态。

调用关系:它会在输入还不足以搜索时被调用。它把清理选择位置的活交给 ScrollState::reset,这样下一次真正有结果时,不会沿用旧的选中行。

调用图:调用 1 个内部函数(reset)。

FileSearchPopup::set_matches67–78 ↗
fn set_matches(&mut self, query: &str, matches: Vec<FileMatch>)

作用:接收一次文件搜索返回的结果,并更新弹窗显示。它会拒绝过时的结果,防止旧搜索覆盖新搜索。

数据流:进去一个查询词和一批 FileMatch 文件匹配结果 → 它先确认查询词等于 pending_query;不等就直接丢弃。相等时,它把这个查询设为当前显示查询,只保留最多 MAX_POPUP_ROWS 条结果,取消 waiting,并修正当前选中位置和可见滚动范围 → 出来时弹窗显示这次有效搜索的第一页结果。

调用关系:它是在搜索结果回来时接棒的函数。它依赖 FileSearchPopup::set_query 之前记录的 pending_query 来判断结果是否过期,并把选中行修正交给 ScrollState::clamp_selection,把滚动位置修正交给 ScrollState::ensure_visible。测试 tests::set_matches_keeps_only_the_first_page_of_results 专门检查它不会保留超过一页的结果。

调用图:调用 2 个内部函数(clamp_selection, ensure_visible)。

FileSearchPopup::move_up81–85 ↗
fn move_up(&mut self)

作用:把文件列表里的选中光标向上移动一格。到顶部时会按 ScrollState 的规则处理回绕,并保证选中项仍然看得见。

数据流:进去没有参数,但读取当前 matches 的数量和滚动状态 → 它让 ScrollState::move_up_wrap 移动选中位置,再用 ScrollState::ensure_visible 调整滚动窗口 → 出来时选中行变了,必要时列表也滚动了。

调用关系:它通常对应用户按上箭头。它自己不关心具体文件名,只把“有多少行”和“怎么移动”交给 ScrollState,之后 render_ref 会按新的状态把高亮行画出来。

调用图:调用 2 个内部函数(ensure_visible, move_up_wrap)。

FileSearchPopup::move_down88–92 ↗
fn move_down(&mut self)

作用:把文件列表里的选中光标向下移动一格。它也会处理滚动,让用户移动选择时不会跑到屏幕外。

数据流:进去没有参数,但读取当前 matches 的数量和滚动状态 → 它让 ScrollState::move_down_wrap 移动选中位置,再调用 ScrollState::ensure_visible 保证选中项在弹窗可见区域内 → 出来时选择位置和滚动位置可能都更新了。

调用关系:它通常对应用户按下箭头。它和 FileSearchPopup::move_up 是一对,负责用户在结果列表里导航;最终显示效果由 FileSearchPopup::render_ref 根据更新后的 state 画出来。

调用图:调用 2 个内部函数(ensure_visible, move_down_wrap)。

FileSearchPopup::selected_match94–99 ↗
fn selected_match(&self) -> Option<&PathBuf>

作用:取出当前被用户选中的文件路径。调用者可以用它知道用户准备选择哪个文件。

数据流:进去没有参数 → 它读取 ScrollState 里的 selected_idx;如果有选中编号,并且这个编号在 matches 范围内,就取出对应 FileMatch 的 path → 出来是一个文件路径引用,或者在没有选中项时返回 None。

调用关系:它通常会在用户确认选择时被用到。它不改变弹窗,只把当前选择结果提供给外部流程,让外部去执行后续动作,比如插入这个文件引用。

FileSearchPopup::calculate_required_height101–109 ↗
fn calculate_required_height(&self) -> u16

作用:计算这个弹窗当前至少需要多高。即使没有结果,也会保留一行,让用户能看到 loading 或 no matches 这样的提示。

数据流:进去没有参数 → 它查看当前 matches 的数量,把行数限制在 1 到 MAX_POPUP_ROWS 之间 → 出来一个高度数字,供布局系统安排弹窗空间。

调用关系:它在界面布局时提供尺寸建议。FileSearchPopup::set_matches 会影响 matches 数量,因此也会影响这里算出的高度;测试里会检查结果很多时高度最多就是 MAX_POPUP_ROWS。

FileSearchPopup::render_ref113–153 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

作用:把文件搜索弹窗真正画到终端缓冲区里。缓冲区可以理解成一张还没显示出来的“屏幕草稿纸”。

数据流:进去一个绘制区域 Rect 和一个可写的 Buffer → 它把每个 FileMatch 转成 GenericDisplayRow,也就是通用列表行;如果有匹配字符位置,就带上高亮信息。然后根据 waiting 决定空列表时显示 loading... 还是 no matches,给区域加左边距,最后交给 render_rows 画出来 → 出来时 Buffer 被写入弹窗内容。

调用关系:它是终端界面刷新时被 ratatui 的 WidgetRef 机制调用的绘制入口。它不负责搜索,也不负责移动选择;这些状态由 set_query、set_matches、move_up、move_down 提前准备好。它把最终绘制工作交给 render_rows,并用 Insets::tlbr 和 RectExt::inset 调整显示位置。

调用图:调用 2 个内部函数(render_rows, tlbr);外部调用 2 个(inset, new)。

tests::file_match162–170 ↗
fn file_match(index: usize) -> FileMatch

作用:给测试快速造一个假的文件搜索结果。这样测试不用真的去扫描磁盘。

数据流:进去一个数字 index → 它把这个数字拼进类似 src/file_00.rs 的路径里,并填好分数、匹配类型、根目录等字段 → 出来一个可比较的 FileMatch 测试数据。

调用关系:它只在测试模块里服务于 tests::set_matches_keeps_only_the_first_page_of_results。它用格式化字符串生成不同文件名,用 PathBuf::from 把文字路径变成路径对象。

调用图:外部调用 2 个(from, format!)。

tests::set_matches_keeps_only_the_first_page_of_results173–183 ↗
fn set_matches_keeps_only_the_first_page_of_results()

作用:验证搜索结果太多时,弹窗只保留第一页能显示的数量。这个测试防止将来有人改代码时不小心让弹窗保存或显示过多结果。

数据流:进去没有外部输入 → 它新建弹窗,设置查询词 file,再喂给它超过 MAX_POPUP_ROWS 的假结果 → 最后用断言确认 popup.matches 只剩前 MAX_POPUP_ROWS 条,并且需要的高度也等于最大弹窗行数。

调用关系:这是 FileSearchPopup::set_matches 行为的保护网。它使用 FileSearchPopup::new 创建状态,用 tests::file_match 生成测试结果,再通过断言检查最终状态是否符合预期。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

tui/src/bottom_pane/skill_popup.rs源码 ↗
domain_logic用户输入提及内容、弹出候选列表、用键盘选择并插入时活跃

当用户想在输入框里插入一个技能或插件时,屏幕底部会弹出一个小窗口。这个文件就是这个小窗口的核心。它保存所有可选项,也保存用户当前输入的搜索词和光标选中的位置。它会用模糊匹配,也就是“不必完全打对也能搜到”的搜索方式,找出合适的项目;再按匹配质量、类型优先级和名字排序。列表太长时,它不会把所有内容都画出来,而是配合滚动状态,只显示一小段,同时保证当前选中的那一项在可见范围内。最后,它把候选项画成一行一项的列表,并在底部提示“按 Enter 插入,按 Esc 关闭”。没有这个文件,用户就只能靠记忆手打技能名,既慢又容易输错。

函数细节22
SkillPopup::new40–46 ↗
fn new(mentions: Vec<MentionItem>) -> Self

作用:创建一个新的技能选择弹窗。外部把所有候选项交给它,它从空搜索词和初始滚动位置开始。

数据流:进去的是一组 MentionItem 候选项 → 函数把它们存起来,同时建立空的 query 和新的 ScrollState 滚动状态 → 出来的是一个可以马上显示和搜索的 SkillPopup。

调用关系:它是这个弹窗的起点。实际界面同步提及弹窗时会用它,多个测试也用它搭出一个可验证的弹窗样本。

调用图:调用 1 个内部函数(new);被 5 处调用(sync_mention_popup, display_name_match_sorting_beats_worse_secondary_search_term_matches, filtered_mentions_preserve_results_beyond_popup_height, query_match_score_sorts_before_plugin_rank_bias, scrolling_mentions_shifts_rendered_window_snapshot);外部调用 1 个(new)。

SkillPopup::set_mentions48–51 ↗
fn set_mentions(&mut self, mentions: Vec<MentionItem>)

作用:替换弹窗里的候选项列表。比如技能列表刷新了,就用它把旧列表换成新列表。

数据流:进去的是新的 MentionItem 列表 → 函数覆盖原来的 mentions → 再调用 clamp_selection,防止原来选中的位置在新列表里越界。

调用关系:它通常由外部界面代码在候选数据变化时调用。它不自己决定选中哪项,而是把修正选择位置的工作交给 SkillPopup::clamp_selection。

调用图:调用 1 个内部函数(clamp_selection)。

SkillPopup::set_query53–56 ↗
fn set_query(&mut self, query: &str)

作用:更新用户当前输入的搜索词。搜索词一变,列表内容和选中位置都可能要跟着调整。

数据流:进去的是一段字符串 query → 函数复制成自己的 query → 再调用 clamp_selection,保证筛选后的列表里仍有合理的选中项。

调用关系:它在用户继续打字或删字时被外部调用。它负责触发后续筛选状态的修正,具体修正交给 SkillPopup::clamp_selection。

调用图:调用 1 个内部函数(clamp_selection)。

SkillPopup::calculate_required_height58–62 ↗
fn calculate_required_height(&self, _width: u16) -> u16

作用:计算这个弹窗需要多高。界面布局要先知道它占几行,才能给它留位置。

数据流:进去的是宽度参数,但这里实际没有用到 → 函数先调用 filtered 找到当前匹配项,再用 rows_from_matches 转成显示行 → 按最多 MAX_POPUP_ROWS 行显示,并额外加 2 行给边距或提示,返回高度。

调用关系:测试里的 tests::render_popup 会先调用它决定缓冲区大小。它依赖 SkillPopup::filtered 和 SkillPopup::rows_from_matches 得到当前应该显示的行数。

调用图:调用 2 个内部函数(filtered, rows_from_matches);被 1 处调用(render_popup)。

SkillPopup::move_up64–68 ↗
fn move_up(&mut self)

作用:让选中项向上移动一格。到顶后会绕到末尾,像菜单循环选择一样。

数据流:它读取当前筛选后的项目数量 → 调用滚动状态的 move_up_wrap 改变选中下标 → 再调用 ensure_visible,让新的选中项不会跑到可见窗口外。

调用关系:用户按上箭头时会走到这里。它先通过 SkillPopup::filtered_items 知道当前有多少项,再把移动和可见性维护交给 ScrollState。

调用图:调用 3 个内部函数(ensure_visible, move_up_wrap, filtered_items)。

SkillPopup::move_down70–74 ↗
fn move_down(&mut self)

作用:让选中项向下移动一格。到底后会绕回开头,方便连续按键浏览。

数据流:它读取当前筛选后的项目数量 → 调用 move_down_wrap 更新选中位置 → 再调用 ensure_visible 调整滚动窗口 → 弹窗状态被更新但不直接返回列表。

调用关系:用户按下箭头时会用到它。测试 tests::scrolling_mentions_shifts_rendered_window_snapshot 通过反复调用它来确认滚动窗口会正确移动。

调用图:调用 3 个内部函数(ensure_visible, move_down_wrap, filtered_items)。

SkillPopup::selected_mention76–81 ↗
fn selected_mention(&self) -> Option<&MentionItem>

作用:取出当前被用户选中的那一个候选项。按 Enter 插入时,需要靠它知道该插入什么。

数据流:它读取当前筛选结果和 ScrollState 里的 selected_idx → 用选中下标找到原始 mentions 里的项目 → 如果没有选中项或下标无效,就返回 None。

调用关系:它是弹窗和插入动作之间的接口。它调用 SkillPopup::filtered_items,确保拿到的是当前搜索结果中的选中项,而不是旧列表里的任意项。

调用图:调用 1 个内部函数(filtered_items)。

SkillPopup::clamp_selection83–87 ↗
fn clamp_selection(&mut self)

作用:把当前选中位置修正到合法范围内。列表变短或搜索词改变时,原来的选择可能已经不存在了。

数据流:它先得到当前筛选后的数量 → 调用 ScrollState 的 clamp_selection 把选中下标夹回有效范围 → 再调用 ensure_visible 保证选中项能看见。

调用关系:SkillPopup::set_mentions 和 SkillPopup::set_query 都会调用它。它是数据变化后的一道安全闸,避免界面指向不存在的项目。

调用图:调用 3 个内部函数(clamp_selection, ensure_visible, filtered_items);被 2 处调用(set_mentions, set_query)。

SkillPopup::filtered_items89–91 ↗
fn filtered_items(&self) -> Vec<usize>

作用:只返回当前匹配项目在原始列表里的编号。别人如果只关心“哪些项匹配”,不用处理匹配分数和高亮信息。

数据流:它调用 filtered 得到带有匹配位置和分数的完整结果 → 丢掉额外信息,只保留每个项目的原始下标 → 返回下标列表。

调用关系:move_up、move_down、selected_mention 和 clamp_selection 都用它来判断当前列表长度或定位选中项。它是完整筛选结果的简化出口。

调用图:调用 1 个内部函数(filtered);被 4 处调用(clamp_selection, move_down, move_up, selected_mention)。

SkillPopup::rows_from_matches93–128 ↗
fn rows_from_matches(
        &self,
        matches: Vec<(usize, Option<Vec<usize>>, i32)>,
    ) -> Vec<GenericDisplayRow>

作用:把搜索结果变成能画在屏幕上的行。它关心的是每行显示什么名字、描述和匹配高亮。

数据流:进去的是 filtered 产生的匹配结果,里面有原始下标、匹配字符位置和分数 → 函数找到对应 MentionItem,截短过长名字,拼出分类标签和描述 → 出来的是 GenericDisplayRow 列表,供渲染函数画出来。

调用关系:SkillPopup::calculate_required_height 用它估算行数,SkillPopup::render_ref 用它生成真正要画的内容。它把搜索层的数据翻译成显示层能懂的格式。

调用图:被 2 处调用(calculate_required_height, render_ref)。

SkillPopup::filtered130–181 ↗
fn filtered(&self) -> Vec<(usize, Option<Vec<usize>>, i32)>

作用:根据用户输入筛选和排序候选项。这是弹窗“搜得准、排得合理”的关键。

数据流:它读取 query 和全部 mentions → 如果搜索词为空,就保留全部项目;如果不为空,就先匹配显示名,再匹配额外搜索词 → 记录匹配位置和分数 → 最后按显示名直接命中优先、分数、sort_rank 和名字排序,返回完整匹配结果。

调用关系:calculate_required_height、filtered_items 和 render_ref 都依赖它。它会调用 fuzzy_match 这个外部模糊匹配工具,负责把用户不完整的输入和候选文字对上。

调用图:被 3 处调用(calculate_required_height, filtered_items, render_ref);外部调用 2 个(new, fuzzy_match)。

SkillPopup::render_ref185–217 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

作用:把弹窗真正画到终端缓冲区里。缓冲区可以理解成一张还没显示到屏幕上的字符画布。

数据流:进去的是可绘制区域 area 和可写入的 Buffer → 函数把区域拆成列表区和提示区 → 筛选候选项并转成显示行 → 调用 render_rows_single_line 画列表 → 如果有空间,再画 Enter/Esc 提示。

调用关系:它是 ratatui 的 WidgetRef 渲染入口,测试里的 tests::render_popup 会调用它生成快照。它把筛选、行转换、列表渲染和提示文字串在一起。

调用图:调用 5 个内部函数(render_rows_single_line, filtered, rows_from_matches, skill_popup_hint_line, tlbr);被 1 处调用(render_popup);外部调用 2 个(Length, vertical)。

skill_popup_hint_line220–228 ↗
fn skill_popup_hint_line() -> Line<'static>

作用:生成弹窗底部那行快捷键提示。它告诉用户按 Enter 插入,按 Esc 关闭。

数据流:它没有输入 → 把普通文字和经过 key_hint 格式化的按键名拼成一行 Line → 返回给渲染函数使用。

调用关系:SkillPopup::render_ref 在需要画提示区时调用它。它只负责造提示文字,不负责决定提示画在哪里。

调用图:被 1 处调用(render_ref);外部调用 2 个(from, vec!)。

tests::mention_item237–247 ↗
fn mention_item(index: usize) -> MentionItem

作用:给测试快速造一个标准候选项。这样测试不用每次手写一大段 MentionItem 字段。

数据流:进去的是数字 index → 函数用这个数字拼出显示名、描述、插入文本、搜索词和路径 → 返回一个带 [Skill] 标签、sort_rank 为 1 的 MentionItem。

调用关系:多个测试用它批量生成候选项,尤其是测试列表超过弹窗高度时的滚动和高度计算。

调用图:外部调用 2 个(format!, vec!)。

tests::ranked_mention_item249–267 ↗
fn ranked_mention_item(
        display_name: &str,
        search_terms: &[&str],
        category_tag: &str,
        sort_rank: u8,
    ) -> MentionItem

作用:给测试造一个可指定分类和排序权重的候选项。它用来比较 Skill 和 Plugin 谁该排前面。

数据流:进去的是显示名、搜索词数组、分类标签和 sort_rank → 函数把这些字段装进 MentionItem,描述和路径留空 → 返回一个用于排序测试的候选项。

调用关系:tests::named_mention_item 和 tests::plugin_mention_item 都通过它生成不同类型的测试数据。它是测试数据工厂的基础版本。

调用图:外部调用 1 个(format!)。

tests::named_mention_item269–271 ↗
fn named_mention_item(display_name: &str, search_terms: &[&str]) -> MentionItem

作用:给测试造一个普通 Skill 候选项。它把常用默认值包起来,让测试更好读。

数据流:进去的是显示名和搜索词 → 函数固定分类为 [Skill]、sort_rank 为 1 → 返回 ranked_mention_item 生成的 MentionItem。

调用关系:排序相关测试大量使用它。它把具体构造工作交给 tests::ranked_mention_item,让测试只关注名字和搜索词。

调用图:外部调用 1 个(ranked_mention_item)。

tests::plugin_mention_item273–275 ↗
fn plugin_mention_item(display_name: &str, search_terms: &[&str]) -> MentionItem

作用:给测试造一个 Plugin 候选项。它用来检查插件的排序偏好不会压过更好的搜索匹配。

数据流:进去的是显示名和搜索词 → 函数固定分类为 [Plugin]、sort_rank 为 0 → 返回 ranked_mention_item 生成的 MentionItem。

调用关系:tests::query_match_score_sorts_before_plugin_rank_bias 用它制造插件样本,再和普通 Skill 样本比较排序。

调用图:外部调用 1 个(ranked_mention_item)。

tests::filtered_mentions_preserve_results_beyond_popup_height278–297 ↗
fn filtered_mentions_preserve_results_beyond_popup_height()

作用:验证筛选结果不会因为弹窗最多只显示几行就被截断。显示窗口有限,不代表数据结果只能有这么多。

数据流:它生成比 MAX_POPUP_ROWS 多两个的候选项 → 创建 SkillPopup → 读取 filtered_items 得到全部匹配名字 → 断言所有项目都还在,同时断言需要的高度只按最大显示行数计算。

调用关系:这个测试直接调用 SkillPopup::new,并检查 SkillPopup 的筛选和高度逻辑配合是否正确,防止渲染限制误伤真实结果。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

tests::render_popup299–304 ↗
fn render_popup(popup: &SkillPopup, width: u16) -> String

作用:把一个弹窗渲染成字符串,方便测试比较画面快照。它相当于给弹窗拍一张文字照片。

数据流:进去的是 SkillPopup 和宽度 → 先调用 calculate_required_height 决定区域高度 → 创建空 Buffer → 调用 render_ref 写入内容 → 把 Buffer 格式化成字符串返回。

调用关系:tests::scrolling_mentions_shifts_rendered_window_snapshot 用它生成快照。它串起高度计算和实际渲染,是测试 UI 输出的辅助工具。

调用图:调用 2 个内部函数(calculate_required_height, render_ref);外部调用 3 个(empty, new, format!)。

tests::scrolling_mentions_shifts_rendered_window_snapshot307–315 ↗
fn scrolling_mentions_shifts_rendered_window_snapshot()

作用:验证列表滚动后,屏幕上显示的窗口真的会跟着移动。也就是选中项往下走时,列表不能还停在原地。

数据流:它创建一个超过最大显示行数的弹窗 → 连续调用 move_down 让选中项下移到需要滚动的位置 → 调用 render_popup 生成画面 → 用快照断言画面符合预期。

调用关系:这个测试覆盖 SkillPopup::new、SkillPopup::move_down 和渲染路径。它关注 ScrollState 和 render_ref 配合后的最终可见效果。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_snapshot!)。

tests::display_name_match_sorting_beats_worse_secondary_search_term_matches318–347 ↗
fn display_name_match_sorting_beats_worse_secondary_search_term_matches()

作用:验证“显示名直接匹配”应该排在只靠额外搜索词匹配的结果前面。用户看到的名字更相关,就应该更靠前。

数据流:它创建多种 Skill 候选项 → 把查询词设为 pr → 读取筛选后的名字顺序 → 断言显示名匹配更好的项目排在前面,较弱的额外搜索词匹配排后面。

调用关系:这个测试使用 SkillPopup::new 和 set_query 后间接检查 SkillPopup::filtered 的排序规则,防止搜索结果看起来反直觉。

调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, vec!)。

tests::query_match_score_sorts_before_plugin_rank_bias350–389 ↗
fn query_match_score_sorts_before_plugin_rank_bias()

作用:验证搜索匹配质量比插件排序偏好更重要。即使 Plugin 的 sort_rank 更小,也不能压过明显更匹配的 Skill。

数据流:它创建一个 Plugin 和多个 Skill 候选项 → 设置查询词 pr → 收集筛选结果里的名字和分类标签 → 断言更符合查询的 Skill 排在前面,Plugin GitHub 排到后面。

调用关系:这个测试检查 SkillPopup::filtered 的排序优先级。它用 tests::plugin_mention_item 和 tests::named_mention_item 构造对比样本,确保排序规则符合用户搜索直觉。

调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, vec!)。

tui/src/bottom_pane/chat_composer.rs源码 ↗
domain_logicrequest handling / main loop

可以把这个文件理解成聊天窗口底部那块“多功能输入台”的核心。它不只是保存一段文字,还要处理很多真实使用场景:用户可能粘贴一大段内容,系统要先用占位符代替;用户可能粘贴图片路径,系统要把它变成图片附件;用户输入 @ 或 / 时,要弹出候选菜单;用户按上下键时,要找历史消息;用户开 Vim 模式时,光标和状态提示也要跟着变。这里的 ChatComposer 就像一个小控制台,里面有草稿、附件、弹窗、页脚提示、历史记录和快捷键配置几块零件。按键或外部状态变化进来后,它更新这些零件,再让界面重新显示正确内容。 这部分代码主要围绕一个叫 ChatComposer 的组件工作。可以把它想成聊天窗口底部的前台接待员:用户每按一个键,它先判断是不是在选弹窗、是不是在粘贴、是不是斜杠命令、是不是文件或技能提及;然后决定是继续编辑、打开候选列表、提交消息,还是把输入放进队列。它还会把“@文件”“$技能/插件/应用”这类特殊文字做成原子元素,像贴纸一样嵌在文本里,避免用户一不小心改坏引用。提交前,它会清理空白、展开大段粘贴占位符、检查命令是否存在、限制输入长度,并把可恢复的信息存进历史。整体作用是让一个看似普通的输入框,具备聊天、命令行、文件选择、插件提及和 Vim 模式等复杂行为。 从这段代码看,ChatComposer 像一个终端里的“聊天输入台”。它不只是放文字,还会显示快捷键提示、状态栏、上下文用量、插件提及高亮、远程图片附件、命令/文件/技能弹窗等。它会根据当前情况调整底部提示:比如任务正在跑时提示可以排队提交,关机时禁用输入,按 Esc 时显示返回提示。渲染时,它先算每块区域该占多少行,再把弹窗、页脚、输入提示符、文本框和占位文字画到屏幕缓冲区里。后半部分是一组测试,像拍照对比一样确认这些细节不会被改坏:页脚有没有空行、闪烁提示是否优先、Shell 模式颜色、插件 @ 提及颜色、Vim 模式下按键行为等。 从这一段能看出,这个文件的重点是给聊天输入框做大量边界测试。聊天输入框不只是一个普通文本框:它要识别 /plan 这类斜杠命令,要支持 Vim 模式(像 Vim 编辑器一样有“插入”和“普通”两种状态),要处理很大的粘贴内容,还要保存被 Ctrl+C 清掉的草稿,方便用户找回。它还支持图片占位符、@ 文件引用、$ 插件/应用/技能引用,以及底部快捷键提示。可以把它理解成一个很忙的前台接待员:用户按什么键,它既要把字收好,又要判断是不是命令、是不是提示弹窗、是不是要提交,还不能把用户的草稿、图片和粘贴内容弄丢。 可以把 ChatComposer 理解成聊天窗口底部那块“输入栏”。它不只是放文字,还要判断用户是在正常打字、快速粘贴、输入 /model 这类斜杠命令,还是在任务运行时先把话排队。这里的测试像一张很细的体检表:检查中文、日文、ASCII 字符混在一起时不会丢;快速粘贴时回车要当换行而不是提交;超大粘贴先显示占位符,真正提交时再还原全文;输入太长要报错并保留草稿;快捷键改掉后旧按键不能偷偷生效;命令弹窗的排序和画面也要稳定。它的重要性在于,输入框是用户每天碰到最多的地方,小小的误判就会让内容提前发送、丢失,或者命令执行错。 从这段可以看出,这个文件不只是一个普通文本框。它像聊天软件里的“发送栏”,但要懂很多特殊规则:以斜杠开头的是内置命令,以感叹号开头的是要原样发送的 shell 命令;大段粘贴会先显示成占位符,真正提交时再换回原文;图片也用“[Image #1]”这样的占位符放进文字里,并保留真实路径;上下键或 Vim 的 j/k 可以翻历史记录。这里的测试专门盯住这些容易混淆的边界情况,确保用户看到的占位符、实际提交的内容、附件列表和历史恢复都能对得上。 从这一段测试可以看出,ChatComposer 不只是一个普通文本框,更像一个“前台收银台”:它接收用户输入,然后判断这是普通消息、斜杠命令、图片、远程图片,还是一次很大的粘贴。它还要维护一些看不见但很关键的账本,比如本地图片对应哪个占位文字、远程图片占了几个编号、大段粘贴是否先用占位符显示、外部编辑器改完内容后哪些附件还在。这里的测试重点检查各种容易出错的边角情况:删除图片后编号要重新排好;路径里以斜杠开头不能误认为命令;人类慢慢打字要实时显示,机器式快速粘贴要先缓存;关闭中时输入框要禁用并隐藏光标。简单说,这些测试是在保证输入框“看起来顺手,用起来不丢东西”。

函数细节367
user_input_too_large_message266–270 ↗
fn user_input_too_large_message(actual_chars: usize) -> String

作用:生成一条“输入太长了”的提示文字。用户提交的内容超过上限时,会用它告诉用户实际输入了多少字符。

数据流:输入实际字符数 → 把固定上限和实际数量拼进一句英文提示 → 返回这句提示,不改动任何状态。

调用关系:提交前整理文本时会调用它,用来把长度检查失败变成用户能看懂的错误信息。

调用图:被 1 处调用(prepare_submission_text_with_options);外部调用 1 个(format!)。

ChatComposerConfig::default329–335 ↗
fn default() -> Self

作用:给聊天输入框提供默认配置。默认情况下,弹窗、斜杠命令和图片粘贴都打开。

数据流:没有输入 → 创建一个配置对象,三个开关都设为 true → 返回这个配置。

调用关系:创建普通 ChatComposer 时会用它,让新输入框一开始就是功能完整的状态。

调用图:被 1 处调用(new)。

ChatComposerConfig::plain_text343–349 ↗
fn plain_text() -> Self

作用:创建一个“纯文本模式”的配置。这个模式会关掉弹窗、斜杠命令和图片粘贴,适合只想要普通文本框的场景。

数据流:没有输入 → 创建一个配置对象,三个增强功能开关都设为 false → 返回这个配置。

调用关系:带自定义键位创建输入框时可能会用它,避免输入框弹出额外菜单或识别特殊内容。

调用图:被 2 处调用(new_with_keymap, new_with_keymap)。

plan_mode_nudge_line418–428 ↗
fn plan_mode_nudge_line() -> Line<'static>

作用:做出一行提示,提醒用户可以切到 Plan mode(计划模式)。它告诉用户按 Shift+Tab 使用计划模式,按 Esc 关闭提示。

数据流:没有输入 → 组装带颜色和快捷键提示的一行文字 → 返回给渲染界面使用。

调用关系:界面绘制页脚附近提示时会调用它,让计划模式的引导出现在输入框下方。

调用图:被 1 处调用(render_with_mask_and_textarea_right_reserve);外部调用 2 个(from, vec!)。

ChatComposer::slash_input431–438 ↗
fn slash_input(&self) -> SlashInput<'_>

作用:准备斜杠命令解析器需要的信息。斜杠命令就是用户输入 /help 这类命令时触发的快捷功能。

数据流:读取当前是否允许斜杠命令、是否处于 bash 模式、内置命令开关、服务档位命令列表 → 创建 SlashInput → 返回给命令识别流程。

调用关系:提交消息、同步命令弹窗、尝试执行斜杠命令时都会先用它拿到当前可用命令环境。

调用图:调用 3 个内部函数(builtin_command_flags, slash_commands_enabled, new);被 5 处调用(handle_submission_with_time, prepare_submission_text_with_options, sync_command_popup, try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args)。

ChatComposer::builtin_command_flags440–452 ↗
fn builtin_command_flags(&self) -> BuiltinCommandFlags

作用:汇总哪些内置斜杠命令当前可用。比如协作模式、插件、连接器、目标、个性化等命令是否应该出现。

数据流:读取 ChatComposer 内部的一组功能开关 → 打包成 BuiltinCommandFlags → 返回给斜杠命令系统。

调用关系:它只服务于 slash_input,slash_input 再把这些开关交给命令解析和弹窗。

调用图:被 1 处调用(slash_input)。

ChatComposer::new454–469 ↗
fn new(
        has_input_focus: bool,
        app_event_tx: AppEventSender,
        enhanced_keys_supported: bool,
        placeholder_text: String,
        disable_paste_burst: bool,
    ) -> Self

作用:创建一个默认功能开启的聊天输入框。外部通常用这个入口来初始化底部输入区。

数据流:输入焦点状态、事件发送器、键盘能力、占位文字、粘贴检测开关 → 加上默认配置 → 交给 new_with_config 创建完整对象。

调用关系:它是常规构造入口,把简单参数转成完整配置;测试和应用初始化都会通过它拿到 ChatComposer。

调用图:调用 1 个内部函数(default);被 175 处调用(new, history_search_accepts_matching_entry, history_search_esc_resets_normal_history_navigation, history_search_esc_restores_original_draft, history_search_flushes_buffered_paste_before_snapshot, history_search_flushes_pending_first_char_before_snapshot, history_search_footer_action_hints_are_emphasized, history_search_highlights_matches_until_accepted, history_search_no_match_restores_preview_but_keeps_search_open, history_search_opens_without_previewing_latest_entry (+15 more));外部调用 1 个(new_with_config)。

ChatComposer::new_with_config475–563 ↗
fn new_with_config(
        has_input_focus: bool,
        app_event_tx: AppEventSender,
        enhanced_keys_supported: bool,
        placeholder_text: String,
        disable_paste_burst: bool,

作用:按指定配置创建完整的 ChatComposer。它把草稿、弹窗、历史、页脚、附件、快捷键等内部零件都装好。

数据流:输入焦点、事件发送器、键盘能力、占位文字、粘贴设置和配置 → 建立默认键位和各种初始状态 → 根据粘贴设置做一次集中设置 → 返回新输入框。

调用关系:new 和带键位的构造流程会调用它;它是这个组件真正的组装工厂。

调用图:调用 7 个内部函数(new, footer_insert_newline_key, new, ctrl, plain, defaults, primary_binding);被 2 处调用(new_with_keymap, new_with_keymap);外部调用 5 个(Char, new, default, default, vec!)。

ChatComposer::set_frame_requester565–567 ↗
fn set_frame_requester(&mut self, frame_requester: FrameRequester)

作用:给输入框放入一个“请求重绘界面”的工具。这样状态变化后,输入框可以要求界面刷新。

数据流:输入 FrameRequester → 保存到内部字段 → 没有返回值。

调用关系:外层界面初始化后会把重绘能力交给 ChatComposer,之后组件内部需要刷新时可使用。

ChatComposer::set_skill_mentions569–572 ↗
fn set_skill_mentions(&mut self, skills: Option<Vec<SkillMetadata>>)

作用:更新可被 @ 提到的技能列表。列表变化后,要马上刷新候选弹窗。

数据流:输入可选技能列表 → 存到内部 skills → 调用 sync_popups 让弹窗内容跟上新列表。

调用关系:set_skills 一类外部更新会调用它;它把后端给出的技能信息接到输入提示系统里。

调用图:调用 1 个内部函数(sync_popups);被 1 处调用(set_skills)。

ChatComposer::set_plugin_mentions574–577 ↗
fn set_plugin_mentions(&mut self, plugins: Option<Vec<PluginCapabilitySummary>>)

作用:更新可被 @ 提到或选择的插件能力列表。这样用户输入时能看到最新插件候选。

数据流:输入可选插件列表 → 存到内部 plugins → 同步弹窗。

调用关系:外部插件状态改变时调用它,随后弹窗刷新使用新插件数据。

调用图:调用 1 个内部函数(sync_popups);被 1 处调用(set_plugin_mentions)。

ChatComposer::set_plugins_command_enabled579–581 ↗
fn set_plugins_command_enabled(&mut self, enabled: bool)

作用:打开或关闭插件相关斜杠命令。用于根据功能开关控制用户能不能看到这类命令。

数据流:输入布尔值 → 写入 plugins_command_enabled → 不返回结果。

调用关系:外层设置变更时调用;之后 slash_input 会读取这个开关。

调用图:被 1 处调用(set_plugins_command_enabled)。

ChatComposer::set_token_activity_command_enabled583–585 ↗
fn set_token_activity_command_enabled(&mut self, enabled: bool)

作用:打开或关闭 token 活动相关命令。token 可以理解为模型消耗的文字计量单位。

数据流:输入布尔值 → 写入 token_activity_command_enabled → 不返回结果。

调用关系:外层配置命令可见性时调用;斜杠命令列表会间接受它影响。

调用图:被 1 处调用(set_token_activity_command_enabled)。

ChatComposer::set_mentions_v2_enabled587–591 ↗
fn set_mentions_v2_enabled(&mut self, enabled: bool)

作用:切换新版 @ 提及功能。开启后,历史恢复和弹窗逻辑也要按新版方式工作。

数据流:输入是否开启 → 更新 mentions_v2_enabled → 通知历史记录是否恢复 @ 提及绑定 → 同步弹窗。

调用关系:外部功能开关变化时调用;它同时影响历史、弹窗和当前输入识别。

调用图:调用 2 个内部函数(sync_popups, set_at_mention_restore_enabled);被 1 处调用(set_mentions_v2_enabled)。

ChatComposer::set_image_paste_enabled597–599 ↗
fn set_image_paste_enabled(&mut self, enabled: bool)

作用:控制粘贴图片路径时是否自动变成图片附件。关闭后,图片路径会更像普通文字处理。

数据流:输入布尔值 → 写入配置里的 image_paste_enabled → 不返回结果。

调用关系:handle_paste 会读取这个开关决定是否尝试识别图片路径。

调用图:被 1 处调用(set_image_paste_enabled)。

ChatComposer::set_connector_mentions601–604 ↗
fn set_connector_mentions(&mut self, connectors_snapshot: Option<ConnectorsSnapshot>)

作用:更新连接器相关的 @ 候选内容。连接器可以理解为外部服务或数据源入口。

数据流:输入连接器快照 → 保存到 connectors_snapshot → 同步弹窗。

调用关系:连接器状态更新时调用;弹窗随后能展示最新可选项。

调用图:调用 1 个内部函数(sync_popups);被 1 处调用(set_connectors_snapshot)。

ChatComposer::take_mention_bindings606–623 ↗
fn take_mention_bindings(&mut self) -> Vec<MentionBinding>

作用:取走当前文本里仍然有效的 @ 提及绑定。绑定记录了一个显示出来的提及实际对应哪个路径或对象。

数据流:读取当前文本里的提及元素 → 从草稿绑定表里挑出仍匹配的项 → 清空旧绑定表 → 返回按文本顺序排列的绑定。

调用关系:提交或外部取绑定时会调用它,避免把已经被用户删掉或改掉的提及也提交出去。

调用图:调用 1 个内部函数(current_mention_elements);被 1 处调用(take_mention_bindings);外部调用 1 个(new)。

ChatComposer::set_collaboration_modes_enabled625–627 ↗
fn set_collaboration_modes_enabled(&mut self, enabled: bool)

作用:打开或关闭协作模式相关命令。协作模式是否可用会影响斜杠命令候选。

数据流:输入布尔值 → 写入 collaboration_modes_enabled → 不返回结果。

调用关系:外层功能配置调用;slash_input 之后会把它带给命令系统。

调用图:被 1 处调用(set_collaboration_modes_enabled)。

ChatComposer::set_connectors_enabled629–631 ↗
fn set_connectors_enabled(&mut self, enabled: bool)

作用:打开或关闭连接器功能命令。用于控制用户是否能通过输入框访问连接器相关能力。

数据流:输入布尔值 → 写入 connectors_enabled → 不返回结果。

调用关系:外层状态更新时调用;内置命令标志会读取它。

调用图:被 1 处调用(set_connectors_enabled)。

ChatComposer::set_service_tier_commands_enabled633–635 ↗
fn set_service_tier_commands_enabled(&mut self, enabled: bool)

作用:控制服务档位相关斜杠命令是否可用。服务档位可理解为不同服务级别或套餐选项。

数据流:输入布尔值 → 写入 service_tier_commands_enabled → 不返回结果。

调用关系:外部配置变化时调用;slash_input 会用它决定命令可见性。

调用图:被 1 处调用(set_service_tier_commands_enabled)。

ChatComposer::set_service_tier_commands637–640 ↗
fn set_service_tier_commands(&mut self, commands: Vec<ServiceTierCommand>)

作用:更新可用的服务档位命令列表。列表变化后,命令弹窗也要刷新。

数据流:输入命令列表 → 保存到 service_tier_commands → 同步弹窗。

调用关系:外层拿到新档位命令后调用;斜杠命令输入会使用这份列表。

调用图:调用 1 个内部函数(sync_popups);被 1 处调用(set_service_tier_commands)。

ChatComposer::set_goal_command_enabled642–644 ↗
fn set_goal_command_enabled(&mut self, enabled: bool)

作用:打开或关闭目标相关命令。目标命令是否出现由这个开关决定。

数据流:输入布尔值 → 写入 goal_command_enabled → 不返回结果。

调用关系:外层功能开关调用;后续斜杠命令构建会读取。

调用图:被 1 处调用(set_goal_command_enabled)。

ChatComposer::set_keymap_bindings652–672 ↗
fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap)

作用:把运行时快捷键设置应用到输入框。用户改键位后,提交、排队、历史搜索、换行等提示和行为都要一起变。

数据流:输入 RuntimeKeymap → 更新内部按键列表、文本编辑器键位、Vim 普通模式键位和页脚提示键 → 不返回结果。

调用关系:外部键位配置刷新时调用;它把统一键位配置分发给输入框和页脚显示。

调用图:调用 2 个内部函数(footer_insert_newline_key, primary_binding);被 1 处调用(set_keymap_bindings)。

ChatComposer::set_collaboration_mode_indicator674–679 ↗
fn set_collaboration_mode_indicator(
        &mut self,
        indicator: Option<CollaborationModeIndicator>,
    )

作用:设置页脚上的协作模式状态提示。比如当前处于哪种协作方式。

数据流:输入可选指示器 → 写入 footer.collaboration_mode_indicator → 不返回结果。

调用关系:外层状态变化时调用;绘制页脚时 mode_indicator_line 会显示它。

调用图:被 1 处调用(set_collaboration_mode_indicator)。

ChatComposer::set_goal_status_indicator681–683 ↗
fn set_goal_status_indicator(&mut self, indicator: Option<GoalStatusIndicator>)

作用:设置页脚上的目标状态提示。这样用户能看到当前目标相关状态。

数据流:输入可选目标状态 → 写入 footer.goal_status_indicator → 不返回结果。

调用关系:外层目标状态更新时调用;页脚右侧状态行会读取它。

调用图:被 1 处调用(set_goal_status_indicator)。

ChatComposer::set_ide_context_active685–687 ↗
fn set_ide_context_active(&mut self, active: bool)

作用:标记 IDE 上下文是否正在参与。IDE 是写代码工具,这里表示编辑器里的上下文是否已接入聊天。

数据流:输入布尔值 → 写入 footer.ide_context_active → 不返回结果。

调用关系:外部 IDE 集成状态变化时调用;页脚状态显示会用到它。

调用图:被 1 处调用(set_ide_context_active)。

ChatComposer::set_personality_command_enabled689–691 ↗
fn set_personality_command_enabled(&mut self, enabled: bool)

作用:打开或关闭个性化相关命令。用于控制用户是否能通过斜杠命令调整助手风格。

数据流:输入布尔值 → 写入 personality_command_enabled → 不返回结果。

调用关系:外层功能开关调用;内置命令标志会读取它。

调用图:被 1 处调用(set_personality_command_enabled)。

ChatComposer::set_side_conversation_active693–695 ↗
fn set_side_conversation_active(&mut self, active: bool)

作用:标记是否正在旁路对话中。旁路对话可以理解为主对话之外的临时分支。

数据流:输入布尔值 → 写入 side_conversation_active → 不返回结果。

调用关系:外部会话状态变化时调用;斜杠命令和页脚状态可能因此改变。

调用图:被 1 处调用(set_side_conversation_active)。

ChatComposer::set_steer_enabled699–699 ↗
fn set_steer_enabled(&mut self, _enabled: bool)

作用:这是一个目前不做事的占位接口。外部可以调用它,但当前版本不会改变任何状态。

数据流:输入布尔值 → 参数被忽略 → 没有输出也没有状态变化。

调用关系:它像预留插座,可能为了兼容上层接口或未来功能而保留。

ChatComposer::popups_enabled701–703 ↗
fn popups_enabled(&self) -> bool

作用:查询弹窗功能是否开启。弹窗包括命令、文件、技能等候选菜单。

数据流:读取配置 popups_enabled → 返回 true 或 false。

调用关系:sync_popups 会调用它,决定要不要显示或更新候选弹窗。

调用图:被 1 处调用(sync_popups)。

ChatComposer::slash_commands_enabled705–707 ↗
fn slash_commands_enabled(&self) -> bool

作用:查询斜杠命令功能是否开启。关闭时,/ 开头的内容不会按命令处理。

数据流:读取配置 slash_commands_enabled → 返回 true 或 false。

调用关系:提交、斜杠输入构建和弹窗同步都会读取它。

调用图:被 3 处调用(handle_submission_with_time, slash_input, sync_popups)。

ChatComposer::image_paste_enabled709–711 ↗
fn image_paste_enabled(&self) -> bool

作用:查询图片粘贴识别是否开启。它决定粘贴图片路径时是否尝试做成附件。

数据流:读取配置 image_paste_enabled → 返回 true 或 false。

调用关系:handle_paste 会调用它来选择图片附件路线还是普通文本路线。

调用图:被 1 处调用(handle_paste)。

ChatComposer::set_windows_degraded_sandbox_active713–715 ↗
fn set_windows_degraded_sandbox_active(&mut self, enabled: bool)

作用:记录 Windows 沙箱是否处于降级状态。沙箱是限制程序权限的隔离环境,降级时某些提升权限命令可能要开放。

数据流:输入布尔值 → 写入 windows_degraded_sandbox_active → 不返回结果。

调用关系:外部系统检测到沙箱状态后调用;内置命令标志会读取它。

调用图:被 1 处调用(set_windows_degraded_sandbox_active)。

ChatComposer::layout_areas716–718 ↗
fn layout_areas(&self, area: Rect) -> [Rect; 4]

作用:计算输入框各部分在屏幕上的位置。它使用默认的右侧预留宽度。

数据流:输入整个可用矩形区域 → 交给 layout_areas_with_textarea_right_reserve,右侧预留为 0 → 返回四块区域。

调用关系:普通布局计算会调用它;复杂场景会直接调用带右侧预留的版本。

调用图:调用 1 个内部函数(layout_areas_with_textarea_right_reserve)。

ChatComposer::layout_areas_with_textarea_right_reserve720–770 ↗
fn layout_areas_with_textarea_right_reserve(
        &self,
        area: Rect,
        textarea_right_reserve: u16,
    ) -> [Rect; 4]

作用:把底部输入区切成输入框、远程图片列表、文本编辑区、弹窗区四块。它确保页脚、弹窗和附件不会互相盖住。

数据流:输入可用区域和文本框右侧预留宽度 → 根据页脚高度、弹窗高度、远程图片行数计算矩形 → 返回四个 Rect。

调用关系:绘制界面和计算光标位置都会用它,是输入框排版的核心尺子。

调用图:调用 3 个内部函数(custom_footer_height, footer_props, tlbr);被 3 处调用(cursor_pos_with_textarea_right_reserve, layout_areas, render_with_mask_and_textarea_right_reserve);外部调用 6 个(Max, Min, vertical, footer_spacing, remote_image_lines, from)。

ChatComposer::cursor_pos_with_textarea_right_reserve784–803 ↗
fn cursor_pos_with_textarea_right_reserve(
        &self,
        area: Rect,
        textarea_right_reserve: u16,
    ) -> Option<(u16, u16)>

作用:计算光标在屏幕上的坐标。输入禁用、选中了远程图片或历史搜索接管时,它会做不同处理。

数据流:输入可用区域和右侧预留宽度 → 检查能否显示普通输入光标 → 用布局结果和文本框状态算出坐标 → 返回坐标或 None。

调用关系:外层渲染光标时调用;它依赖布局函数和文本编辑器自己的光标计算。

调用图:调用 1 个内部函数(layout_areas_with_textarea_right_reserve);被 2 处调用(cursor_pos, cursor_pos)。

ChatComposer::is_empty805–807 ↗
fn is_empty(&self) -> bool

作用:判断当前输入框是否真的空。只有文字为空、不是 bash 模式、也没有附件时才算空。

数据流:读取文本框、bash 模式和附件状态 → 合并判断 → 返回布尔值。

调用关系:清空、快捷键处理、页脚模式判断等地方用它决定当前有没有草稿内容。

调用图:被 6 处调用(composer_is_empty, clear_for_ctrl_c, footer_mode, handle_key_event_without_popup, handle_shortcut_overlay_key, is_empty);外部调用 1 个(is_empty)。

ChatComposer::set_history_metadata811–818 ↗
fn set_history_metadata(
        &mut self,
        thread_id: ThreadId,
        log_id: u64,
        entry_count: usize,
    )

作用:给历史记录模块设置当前线程和日志位置。这样上下键查历史时知道该从哪里找。

数据流:输入线程 ID、日志 ID、条目数 → 转交给 history.set_metadata → 不直接返回数据。

调用关系:外层同步历史信息时调用;真正保存元数据的是 history 子模块。

调用图:调用 1 个内部函数(set_metadata);被 1 处调用(set_history_metadata)。

ChatComposer::on_history_entry_response826–848 ↗
fn on_history_entry_response(
        &mut self,
        log_id: u64,
        offset: usize,
        entry: Option<String>,
    ) -> bool

作用:处理异步返回的历史记录查询结果。查到了就填回输入框,查到搜索结果就更新搜索状态,过期结果就忽略。

数据流:输入日志 ID、偏移量和可选历史文本 → 交给 history 判断结果类型 → 根据结果恢复草稿或更新搜索 → 返回是否处理成功。

调用关系:外层收到历史响应时调用;它把 history 模块的结果接回 ChatComposer 的草稿区。

调用图:调用 2 个内部函数(apply_history_entry, on_entry_response);被 1 处调用(on_history_entry_response)。

ChatComposer::record_replayed_user_message_history850–852 ↗
fn record_replayed_user_message_history(&mut self, entry: HistoryEntry)

作用:把重放过的用户消息记进本地历史。这样用户仍可以用历史导航找回它。

数据流:输入 HistoryEntry → 转交给 history.record_replayed_submission → 不返回结果。

调用关系:外层重放消息后调用;历史模块负责实际记录。

调用图:调用 1 个内部函数(record_replayed_submission);被 1 处调用(record_replayed_user_message_history)。

ChatComposer::handle_paste872–890 ↗
fn handle_paste(&mut self, pasted: String) -> bool

作用:处理用户粘贴的内容。它会统一换行符,识别大段粘贴和图片路径,并更新输入框。

数据流:输入粘贴字符串 → 把 Windows/Mac 换行转成 \n → 大段内容变占位符,小图片路径变附件,普通内容插入文本 → 清理粘贴突发状态并同步弹窗 → 返回 true。

调用关系:粘贴事件、按键处理、粘贴突发刷新都会调用它,是所有粘贴内容进入输入框的总入口。

调用图:调用 5 个内部函数(handle_paste_image_path, image_paste_enabled, insert_str, next_large_paste_placeholder, sync_popups);被 10 处调用(handle_paste, handle_input_basic_with_time, handle_key_event_without_popup, handle_non_ascii_char, handle_paste_burst_flush, handle_submission_with_time, set_disable_paste_burst, handle_paste, handle_paste, handle_paste)。

ChatComposer::handle_paste_image_path892–913 ↗
fn handle_paste_image_path(&mut self, pasted: String) -> bool

作用:判断粘贴内容是不是图片路径,并尝试把它附加为图片。确认能读取图片尺寸才算成功。

数据流:输入粘贴文本 → 规范化成路径 → 读取图片尺寸 → 成功则记录格式并 attach_image,失败返回 false。

调用关系:handle_paste 在图片粘贴开关开启时调用它;成功后文本框会插入一个空格分隔。

调用图:调用 3 个内部函数(attach_image, normalize_pasted_path, pasted_image_format);被 1 处调用(handle_paste);外部调用 4 个(image_dimensions, debug!, info!, trace!)。

ChatComposer::set_disable_paste_burst933–942 ↗
fn set_disable_paste_burst(&mut self, disabled: bool)

作用:打开或关闭“粘贴突发检测”。粘贴突发检测是把很快连续来的字符识别成一次粘贴,避免逐字闪烁。

数据流:输入是否禁用 → 更新 disable_paste_burst → 如果刚刚禁用,就把缓存中的突发粘贴先刷入输入框并清理状态。

调用关系:构造函数和外部设置会调用它;它必要时会回到 handle_paste 走统一粘贴流程。

调用图:调用 1 个内部函数(handle_paste)。

ChatComposer::apply_external_edit949–1024 ↗
fn apply_external_edit(&mut self, text: String)

作用:把外部编辑器改好的文字应用回输入框。它还会保留仍出现在文本里的图片占位符,删掉不再出现的图片附件。

数据流:输入外部编辑后的文本 → 处理 bash 前缀 → 统计图片占位符出现次数 → 保留匹配附件 → 重建文本框元素和光标 → 重新标号本地图片并同步弹窗。

调用关系:用户用外部编辑器编辑草稿后调用;它把纯文本结果还原成带附件元素的输入框状态。

调用图:调用 2 个内部函数(imported_text_for_textarea, sync_popups);被 1 处调用(apply_external_edit);外部调用 3 个(new, new, relabel_local_images)。

ChatComposer::set_vim_enabled1032–1036 ↗
fn set_vim_enabled(&mut self, enabled: bool)

作用:开启或关闭 Vim 编辑模式。Vim 是一种键盘驱动的编辑方式,有普通模式和插入模式。

数据流:输入布尔值 → 设置文本框 Vim 状态 → 清理粘贴突发状态 → 重置页脚模式提示。

调用关系:外部设置或 toggle_vim_enabled 会调用它;页脚和按键处理会受 Vim 状态影响。

调用图:调用 1 个内部函数(reset_mode_after_activity);被 2 处调用(set_vim_enabled, toggle_vim_enabled)。

ChatComposer::toggle_vim_enabled1043–1047 ↗
fn toggle_vim_enabled(&mut self) -> bool

作用:切换 Vim 模式开关。原来开就关,原来关就开。

数据流:读取当前 Vim 状态 → 取反 → 调用 set_vim_enabled 应用 → 返回新的状态。

调用关系:用户触发切换命令时调用;它把实际改状态的工作交给 set_vim_enabled。

调用图:调用 1 个内部函数(set_vim_enabled);被 1 处调用(toggle_vim_enabled)。

ChatComposer::is_vim_enabled1051–1053 ↗
fn is_vim_enabled(&self) -> bool

作用:查询当前是否启用了 Vim 编辑模式。

数据流:读取文本框 Vim 状态 → 返回布尔值。

调用关系:外层状态查询会调用它,用于显示或决定按键行为。

调用图:被 1 处调用(composer_is_vim_enabled)。

ChatComposer::should_handle_vim_insert_escape1060–1064 ↗
fn should_handle_vim_insert_escape(&self, key_event: KeyEvent) -> bool

作用:判断某个 Esc 按键是否应该按 Vim 插入模式退出处理。这样 Esc 不会被误当成普通取消键。

数据流:输入按键事件 → 交给文本框判断 Vim 插入模式规则 → 返回是否应处理。

调用关系:外层和无弹窗按键处理会调用它,避免 Vim 模式和普通界面快捷键冲突。

调用图:被 2 处调用(composer_should_handle_vim_insert_escape, handle_key_event_without_popup)。

ChatComposer::vim_mode_indicator_span1066–1075 ↗
fn vim_mode_indicator_span(&self) -> Option<Span<'static>>

作用:生成页脚里的 Vim 模式小标签。普通模式显示紫色,插入模式显示绿色。

数据流:读取文本框 Vim 模式标签 → 转成带颜色的 Span → 没有 Vim 模式则返回 None。

调用关系:页脚状态行相关函数会调用它,把编辑模式展示给用户。

调用图:被 2 处调用(mode_indicator_line, right_footer_line_with_context)。

ChatComposer::mode_indicator_line1077–1098 ↗
fn mode_indicator_line(&self, show_cycle_hint: bool) -> Option<Line<'static>>

作用:组装页脚右侧的模式提示行。它会把 Vim 状态、协作状态、目标状态和 IDE 上下文提示合在一起。

数据流:输入是否显示切换提示 → 收集 Vim 标签和状态指示器 → 需要时用分隔符拼接 → 返回一行或 None。

调用关系:渲染输入框时调用;它负责把多个状态源合并成用户看到的一行提示。

调用图:调用 2 个内部函数(vim_mode_indicator_span, status_line_right_indicator_line);被 1 处调用(render_with_mask_and_textarea_right_reserve);外部调用 2 个(from, new)。

ChatComposer::current_text_with_pending1112–1124 ↗
fn current_text_with_pending(&self) -> String

作用:拿到当前文本,并把还没真正展开的大段粘贴补回去。这样外部读取时能得到完整内容。

数据流:读取当前文本 → 如果没有 pending_pastes 直接返回 → 否则按文本元素把占位符替换成真实粘贴内容 → 返回完整文本。

调用关系:Ctrl+C、提交前检查、外部查询草稿内容时会调用它。

调用图:调用 2 个内部函数(current_text, current_text_elements);被 5 处调用(composer_text_with_pending, on_ctrl_c, handle_key_event, notes_has_content, on_ctrl_c);外部调用 1 个(expand_pending_pastes)。

ChatComposer::input_enabled1127–1129 ↗
fn input_enabled(&self) -> bool

作用:查询输入框当前能不能输入。

数据流:读取 draft.input_enabled → 返回布尔值。

调用关系:外层状态查询用它判断是否应接收键盘输入。

调用图:被 1 处调用(composer_input_enabled)。

ChatComposer::pending_pastes1131–1133 ↗
fn pending_pastes(&self) -> Vec<(String, String)>

作用:取当前尚未展开的大段粘贴列表。每项包含占位符和真实粘贴内容。

数据流:读取 draft.pending_pastes → 克隆一份返回 → 不改变内部状态。

调用关系:草稿快照和外部查询会调用它,方便保存或恢复大段粘贴。

调用图:被 4 处调用(composer_pending_pastes, draft_snapshot, capture_composer_draft, capture_composer_draft)。

ChatComposer::set_pending_pastes1135–1141 ↗
fn set_pending_pastes(&mut self, pending_pastes: Vec<(String, String)>)

作用:恢复或设置大段粘贴缓存,但只保留当前文本里仍有占位符的项。

数据流:输入 pending_pastes 列表 → 读取当前文本 → 过滤掉文本里不存在的占位符 → 保存剩余项。

调用关系:恢复草稿、应用历史、应用提交草稿时调用,避免留下孤儿粘贴数据。

调用图:调用 1 个内部函数(current_text);被 6 处调用(set_composer_pending_pastes, apply_history_entry, restore_draft, restore_current_draft, apply_submission_draft, restore_current_draft)。

ChatComposer::set_plan_mode_nudge_visible1153–1159 ↗
fn set_plan_mode_nudge_visible(&mut self, visible: bool) -> bool

作用:设置“是否显示计划模式引导”。如果状态没变,它会告诉调用者不用重绘。

数据流:输入目标可见状态 → 与当前状态比较 → 变化则写入并返回 true,否则返回 false。

调用关系:外层控制计划模式提示时调用;返回值可帮助决定是否需要刷新界面。

调用图:被 1 处调用(set_plan_mode_nudge_visible)。

ChatComposer::plan_mode_nudge_visible1162–1164 ↗
fn plan_mode_nudge_visible(&self) -> bool

作用:查询计划模式引导当前是否可见。

数据流:读取 footer.plan_mode_nudge_visible → 返回布尔值。

调用关系:外层状态查询和渲染判断会调用它。

调用图:被 1 处调用(plan_mode_nudge_visible)。

ChatComposer::set_remote_image_urls1166–1170 ↗
fn set_remote_image_urls(&mut self, urls: Vec<String>)

作用:设置远程图片 URL 附件。远程图片是通过网址引用的图片,不是本地文件。

数据流:输入 URL 列表 → 交给 attachments 更新文本框中的远程图片显示 → 同步弹窗。

调用关系:恢复草稿、应用历史和外部设置远程图片时调用。

调用图:调用 1 个内部函数(sync_popups);被 3 处调用(set_remote_image_urls, apply_history_entry, restore_draft);外部调用 1 个(set_remote_image_urls)。

ChatComposer::remote_image_urls1172–1174 ↗
fn remote_image_urls(&self) -> Vec<String>

作用:读取当前远程图片 URL 列表。

数据流:向 attachments 查询远程图片地址 → 返回字符串列表。

调用关系:草稿快照和外部查询会调用它。

调用图:被 2 处调用(remote_image_urls, draft_snapshot);外部调用 1 个(remote_image_urls)。

ChatComposer::take_remote_image_urls1176–1182 ↗
fn take_remote_image_urls(&mut self) -> Vec<String>

作用:取走当前远程图片 URL,并从输入框附件状态里移除它们。

数据流:让 attachments 从文本框中取出并清空远程图片 URL → 同步弹窗 → 返回 URL 列表。

调用关系:提交或外部消费远程图片时调用,取走后输入框不再持有这些远程图片。

调用图:调用 1 个内部函数(sync_popups);被 1 处调用(take_remote_image_urls);外部调用 1 个(take_remote_image_urls)。

ChatComposer::set_text_content1195–1207 ↗
fn set_text_content(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
    )

作用:用指定文字、文本元素和本地图片路径替换当前草稿。它是较简单的设置入口,不带 @ 提及绑定。

数据流:输入文本、文本元素、本地图片路径 → 补一个空的提及绑定列表 → 交给 set_text_content_with_mention_bindings。

调用关系:清空、恢复、应用提交草稿等流程会用它;复杂恢复会直接用带绑定的版本。

调用图:调用 1 个内部函数(set_text_content_with_mention_bindings);被 14 处调用(set_composer_text, clear_for_ctrl_c, apply_submission_to_draft, clear_current_draft, reset_for_request, restore_current_draft, apply_submission_draft, apply_submission_to_draft, clear_notes_and_focus_options, clear_notes_draft (+4 more));外部调用 1 个(new)。

ChatComposer::set_text_content_with_mention_bindings1221–1244 ↗
fn set_text_content_with_mention_bindings(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        mention_bindings: Vec<Ment

作用:完整替换输入框内容,并恢复文本元素、图片附件和 @ 提及绑定。它会先清干净旧草稿再导入新内容。

数据流:输入文本、元素、本地图片路径和提及绑定 → 清空现有文本、粘贴和绑定 → 处理 bash 前缀 → 设置文本元素 → 重建本地图片 → 绑定提及 → 光标放开头并同步弹窗。

调用关系:历史恢复、草稿恢复、提交准备等流程调用它,是恢复复杂草稿的核心入口。

调用图:调用 3 个内部函数(bind_mentions_from_snapshot, imported_text_for_textarea, sync_popups);被 6 处调用(set_composer_text_with_mention_bindings, apply_history_entry, handle_submission_with_time, prepare_submission_text_with_options, restore_draft, set_text_content);外部调用 1 个(reset_local_images)。

ChatComposer::current_cursor1246–1248 ↗
fn current_cursor(&self) -> usize

作用:计算对外看到的光标位置。bash 模式下,文本前面有一个隐藏在编辑框外的 !,所以位置要加 1。

数据流:读取文本框光标和 bash 模式 → 必要时加偏移 → 返回位置数字。

调用关系:快照、外部光标查询和历史导航都会用它统一光标坐标。

调用图:被 3 处调用(cursor, history_navigation_cursor, snapshot_draft)。

ChatComposer::cursor1251–1253 ↗
fn cursor(&self) -> usize

作用:对外返回当前光标位置。

数据流:调用 current_cursor → 返回结果。

调用关系:外层 composer_cursor 查询会调用它;真正计算在 current_cursor。

调用图:调用 1 个内部函数(current_cursor);被 1 处调用(composer_cursor)。

ChatComposer::history_navigation_cursor1255–1266 ↗
fn history_navigation_cursor(&self) -> usize

作用:给历史导航使用的光标位置做特殊修正。它照顾 bash 模式和 Vim 普通模式里的边界情况。

数据流:读取 bash 状态、Vim 状态、文本和光标 → 根据特殊情况返回 0、文本末尾或普通当前光标。

调用关系:按键处理和弹窗同步里判断能否翻历史时使用它。

调用图:调用 2 个内部函数(current_cursor, current_text);被 2 处调用(handle_key_event_without_popup, sync_popups)。

ChatComposer::set_current_cursor1268–1277 ↗
fn set_current_cursor(&mut self, cursor: usize)

作用:恢复对外光标位置到文本框内部。bash 模式下要减掉前面的 ! 偏移。

数据流:输入光标位置 → 若 bash 模式则减 1 且不低于 0 → 限制在文本长度内 → 设置到文本框。

调用关系:restore_draft 用它把快照里的光标放回正确位置。

调用图:被 1 处调用(restore_draft)。

ChatComposer::current_text_elements1279–1287 ↗
fn current_text_elements(&self) -> Vec<TextElement>

作用:读取当前文本里的特殊元素,并把它们的位置调整成对外文本的位置。特殊元素包括图片占位符、大段粘贴占位符等。

数据流:读取文本框元素 → bash 模式下给位置加偏移 → 丢掉无效元素 → 返回元素列表。

调用关系:提交、快照、清空保存历史等流程会调用它。

调用图:被 6 处调用(clear_for_ctrl_c, current_text_with_pending, handle_submission_with_time, prepare_submission_text_with_options, snapshot_draft, text_elements)。

ChatComposer::shift_text_element1289–1297 ↗
fn shift_text_element(element: TextElement, shift: isize) -> Option<TextElement>

作用:把一个文本元素的字节范围整体前移或后移。字节范围就是这段内容在字符串里的起止位置。

数据流:输入元素和偏移量 → 计算新的开始结束位置 → 无效则返回 None,有效则返回改好范围的元素。

调用关系:bash 模式导入和导出文本元素时会用它调整 ! 带来的位置差。

调用图:外部调用 1 个(map_range)。

ChatComposer::snapshot_draft1299–1309 ↗
fn snapshot_draft(&self) -> ComposerDraft

作用:拍一张完整草稿快照。快照包含文字、元素、本地图片、远程图片、提及绑定、待展开粘贴和光标。

数据流:读取当前各类草稿状态 → 打包成 ComposerDraft → 返回。

调用关系:内部保存和恢复当前编辑状态时使用它。

调用图:调用 4 个内部函数(current_cursor, current_text, current_text_elements, snapshot_mention_bindings);外部调用 2 个(local_image_paths, remote_image_urls)。

ChatComposer::restore_draft1311–1331 ↗
fn restore_draft(&mut self, draft: ComposerDraft)

作用:把之前保存的草稿快照恢复回输入框。它会恢复文字、附件、提及、大段粘贴和光标。

数据流:输入 ComposerDraft → 拆出各字段 → 设置远程图片、文本内容、pending_pastes 和光标 → 同步弹窗。

调用关系:草稿切换或恢复当前草稿时调用;它串起多个更小的恢复函数。

调用图:调用 5 个内部函数(set_current_cursor, set_pending_pastes, set_remote_image_urls, set_text_content_with_mention_bindings, sync_popups)。

ChatComposer::set_placeholder_text1334–1336 ↗
fn set_placeholder_text(&mut self, placeholder: String)

作用:设置输入框为空时显示的占位提示文字。

数据流:输入字符串 → 写入 placeholder_text → 不返回结果。

调用关系:外层同步占位文案或恢复草稿时调用;渲染空输入框时会显示它。

调用图:被 4 处调用(set_placeholder_text, restore_current_draft, restore_current_draft, sync_composer_placeholder)。

ChatComposer::move_cursor_to_end1339–1344 ↗
fn move_cursor_to_end(&mut self)

作用:把光标移动到当前文本末尾,并刷新弹窗。常用于外部设置完文本后让用户继续输入。

数据流:读取文本长度 → 设置文本框光标到末尾 → 同步弹窗。

调用关系:设置文本、清空草稿、恢复草稿后常调用它。

调用图:调用 1 个内部函数(sync_popups);被 11 处调用(set_composer_text, set_composer_text_with_mention_bindings, apply_submission_to_draft, clear_current_draft, restore_current_draft, apply_submission_draft, apply_submission_to_draft, clear_notes_and_focus_options, clear_notes_draft, clear_selection (+1 more))。

ChatComposer::move_cursor_to_history_entry_end1346–1354 ↗
fn move_cursor_to_history_entry_end(&mut self)

作用:把光标移动到历史记录内容的合适末尾。Vim 普通模式下使用 Vim 自己的末尾位置规则。

数据流:读取 Vim 状态和文本长度 → 计算目标光标 → 设置光标并同步弹窗。

调用关系:apply_history_entry 恢复历史消息后调用,让用户从历史内容末尾继续编辑。

调用图:调用 1 个内部函数(sync_popups);被 1 处调用(apply_history_entry)。

ChatComposer::imported_text_for_textarea1360–1378 ↗
fn imported_text_for_textarea(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
    ) -> (String, Vec<TextElement>)

作用:把外部文本转换成文本框内部格式。特别是以 ! 开头的 bash 命令,会把 ! 作为模式标记而不是普通编辑文本。

数据流:输入文本和元素 → 如果文本以 ! 开头,开启 bash 模式、去掉 !、元素位置左移;否则关闭 bash 模式并原样返回 → 输出处理后的文本和元素。

调用关系:外部编辑结果和设置草稿内容时调用,保证 bash 模式的内外表示一致。

调用图:被 2 处调用(apply_external_edit, set_text_content_with_mention_bindings)。

ChatComposer::clear_for_ctrl_c1380–1402 ↗
fn clear_for_ctrl_c(&mut self) -> Option<String>

作用:处理 Ctrl+C 清空输入框的场景。它会先把当前草稿记入本地历史,再清空输入区。

数据流:若输入框为空则返回 None → 否则读取当前文本、元素、附件、提及和待粘贴内容 → 清空文本和远程图片 → 重置历史导航并记录历史 → 返回被清掉的文本。

调用关系:外层 clear_composer_for_ctrl_c 调用;它保证误清空的内容还能从历史里找回。

调用图:调用 7 个内部函数(current_text, current_text_elements, is_empty, set_text_content, snapshot_mention_bindings, record_local_submission, reset_navigation);被 1 处调用(clear_composer_for_ctrl_c);外部调用 6 个(new, new, clear_remote_image_urls, local_image_paths, remote_image_urls, take)。

ChatComposer::current_text1405–1411 ↗
fn current_text(&self) -> String

作用:返回当前对外可见的完整文本。bash 模式下会把前面的 ! 加回来。

数据流:读取 bash 模式和文本框内容 → bash 模式拼上 !,否则原样返回 → 输出字符串。

调用关系:提交、快照、清空、历史导航等大量流程都用它作为当前文字来源。

调用图:被 16 处调用(composer_text, clear_for_ctrl_c, current_text_with_pending, draft_snapshot, handle_key_event_without_popup, handle_submission_with_time, history_navigation_cursor, is_bang_shell_command, prepare_submission_text_with_options, set_pending_pastes (+6 more));外部调用 1 个(format!)。

ChatComposer::apply_history_entry1420–1438 ↗
fn apply_history_entry(&mut self, entry: HistoryEntry)

作用:把一条历史记录填回输入框。它会恢复文字、图片、提及绑定和大段粘贴信息。

数据流:输入 HistoryEntry → 拆出文本、元素、附件和绑定 → 设置远程图片和文本内容 → 恢复 pending_pastes → 光标移到历史内容末尾。

调用关系:历史查询返回或按键选择历史时调用;它把历史模块的数据变回可编辑草稿。

调用图:调用 4 个内部函数(move_cursor_to_history_entry_end, set_pending_pastes, set_remote_image_urls, set_text_content_with_mention_bindings);被 2 处调用(handle_key_event_without_popup, on_history_entry_response)。

ChatComposer::text_elements1440–1442 ↗
fn text_elements(&self) -> Vec<TextElement>

作用:对外返回当前文本里的特殊元素列表。

数据流:调用 current_text_elements → 返回元素列表。

调用关系:草稿快照和外部查询会调用它;具体位置修正由 current_text_elements 完成。

调用图:调用 1 个内部函数(current_text_elements);被 4 处调用(composer_text_elements, draft_snapshot, capture_composer_draft, capture_composer_draft)。

ChatComposer::draft_snapshot1444–1453 ↗
fn draft_snapshot(&self) -> ComposerDraftSnapshot

作用:生成给外部使用的草稿快照。它包含文字、元素、图片、提及和待展开粘贴,但不包含内部光标。

数据流:读取当前文本、元素、本地图片、远程图片、提及绑定和 pending_pastes → 打包成 ComposerDraftSnapshot → 返回。

调用关系:外层 composer_draft_snapshot 调用,用于保存当前输入状态。

调用图:调用 6 个内部函数(current_text, local_images, mention_bindings, pending_pastes, remote_image_urls, text_elements);被 1 处调用(composer_draft_snapshot)。

ChatComposer::local_image_paths1456–1458 ↗
fn local_image_paths(&self) -> Vec<PathBuf>

作用:返回当前本地图片附件的文件路径列表。

数据流:向 attachments 查询本地图片路径 → 返回 PathBuf 列表。

调用关系:外层图片路径查询会调用它。

调用图:被 1 处调用(composer_local_image_paths);外部调用 1 个(local_image_paths)。

ChatComposer::status_line_text1461–1463 ↗
fn status_line_text(&self) -> Option<String>

作用:读取页脚状态行里的纯文本内容。没有状态行时返回空。

数据流:调用 footer.status_line_text → 返回可选字符串。

调用关系:外层状态行查询会调用它;实际文本由 footer 保存。

调用图:调用 1 个内部函数(status_line_text);被 1 处调用(status_line_text)。

ChatComposer::local_images1465–1467 ↗
fn local_images(&self) -> Vec<LocalImageAttachment>

作用:返回当前本地图片附件的详细信息,包括占位符等。

数据流:向 attachments 查询本地图片对象 → 返回 LocalImageAttachment 列表。

调用关系:草稿快照、提交草稿应用和外部查询会调用它。

调用图:被 6 处调用(composer_local_images, draft_snapshot, apply_submission_to_draft, capture_composer_draft, apply_submission_to_draft, capture_composer_draft);外部调用 1 个(local_images)。

ChatComposer::mention_bindings1469–1471 ↗
fn mention_bindings(&self) -> Vec<MentionBinding>

作用:返回当前 @ 提及绑定快照。它说明文本里的提及实际对应哪些对象或路径。

数据流:调用 snapshot_mention_bindings → 返回绑定列表。

调用关系:draft_snapshot 用它把提及信息保存到外部快照。

调用图:调用 1 个内部函数(snapshot_mention_bindings);被 1 处调用(draft_snapshot)。

ChatComposer::take_recent_submission_mention_bindings1473–1475 ↗
fn take_recent_submission_mention_bindings(&mut self) -> Vec<MentionBinding>

作用:取走最近一次提交产生的 @ 提及绑定。取走后内部列表会清空。

数据流:从 draft.recent_submission_mention_bindings 中整体取出 → 内部变成空列表 → 返回原列表。

调用关系:提交后外部收集提及信息时调用,避免同一批绑定被重复消费。

调用图:被 1 处调用(take_recent_submission_mention_bindings);外部调用 1 个(take)。

ChatComposer::record_pending_slash_command_history1481–1485 ↗
fn record_pending_slash_command_history(&mut self)

作用:把等待记录的斜杠命令输入写入历史。只有确实有待记录项时才做事。

数据流:检查 pending_slash_command_history → 有则取出并交给 history.record_local_submission → 没有则不变。

调用关系:执行斜杠命令前后调用,保证命令输入也能进入历史记录。

调用图:调用 1 个内部函数(record_local_submission);被 3 处调用(record_pending_slash_command_history, try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args)。

ChatComposer::attach_image1488–1491 ↗
fn attach_image(&mut self, path: PathBuf)

作用:把一个本地图片文件附加到当前输入框。文本框里会出现对应的图片占位元素。

数据流:输入图片路径 → 交给 attachments.attach_image,并传入文本框以便插入占位符 → 不返回结果。

调用关系:粘贴图片、文件弹窗选择图片、外部直接附图都会调用它。

调用图:被 4 处调用(attach_image, handle_key_event_with_file_popup, handle_paste_image_path, insert_selected_file_path);外部调用 1 个(attach_image)。

ChatComposer::take_recent_submission_images1494–1496 ↗
fn take_recent_submission_images(&mut self) -> Vec<PathBuf>

作用:取走最近一次提交里的本地图片路径。取走后内部对应记录会被清空。

数据流:调用 attachments.take_recent_submission_images → 返回路径列表。

调用关系:提交后外部收集图片附件时调用。

调用图:被 1 处调用(take_recent_submission_images);外部调用 1 个(take_recent_submission_images)。

ChatComposer::take_recent_submission_images_with_placeholders1498–1501 ↗
fn take_recent_submission_images_with_placeholders(&mut self) -> Vec<LocalImageAttachment>

作用:取走最近一次提交里的本地图片附件详细信息。相比只拿路径,它还保留占位符对应关系。

数据流:调用 attachments.take_recent_submission_images_with_placeholders → 返回附件对象列表。

调用关系:需要把图片和文本占位符对应起来时,提交后会调用它。

调用图:被 1 处调用(take_recent_submission_images_with_placeholders);外部调用 1 个(take_recent_submission_images_with_placeholders)。

ChatComposer::flush_paste_burst_if_due1513–1515 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

作用:检查粘贴突发缓存是否到时间该刷入输入框。到期就把缓存作为一次粘贴处理。

数据流:读取当前时间 → 调用 handle_paste_burst_flush → 返回是否有处理。

调用关系:主循环定时、测试或输入流程会调用它,避免粘贴缓存一直悬着。

调用图:调用 1 个内部函数(handle_paste_burst_flush);被 6 处调用(flush_paste_burst_if_due, flush_after_paste_burst, type_chars_humanlike, flush_paste_burst_if_due, flush_paste_burst_if_due, flush_paste_burst_if_due);外部调用 1 个(now)。

ChatComposer::is_in_paste_burst1521–1523 ↗
fn is_in_paste_burst(&self) -> bool

作用:查询当前是否正在收集一段疑似粘贴的快速输入。

数据流:读取 paste_burst 活跃状态 → 返回布尔值。

调用关系:按键处理、渲染和外部查询会用它决定是否等待粘贴突发结束。

调用图:被 7 处调用(handle_key_event, is_in_paste_burst, handle_shortcut_overlay_key, render_with_mask_and_textarea_right_reserve, is_in_paste_burst, is_in_paste_burst, is_in_paste_burst)。

ChatComposer::on_file_search_result1533–1557 ↗
fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>)

作用:处理文件搜索返回的匹配结果。它只在用户仍然编辑同一个 @ 查询词时才更新弹窗,避免过期结果乱入。

数据流:输入查询词和匹配文件 → 读取当前正在编辑的 token → 如果当前 token 不再以查询词开头就忽略 → 否则把匹配结果写入文件弹窗或新版提及弹窗。

调用关系:外部文件搜索完成后调用;它把异步搜索结果安全地送回当前弹窗。

调用图:调用 1 个内部函数(current_mentions_v2_token);被 1 处调用(on_file_search_result);外部调用 1 个(current_at_token)。

ChatComposer::show_quit_shortcut_hint1564–1571 ↗
fn show_quit_shortcut_hint(&mut self, key: KeyBinding, has_focus: bool)

作用:显示“退出快捷键”的临时提醒。比如用户第一次按退出键时提示再按一次确认。

数据流:输入快捷键和焦点状态 → 设置过期时间、提示键和页脚模式 → 更新焦点。

调用关系:外层检测到退出快捷键需要提醒时调用;页脚会在超时前显示这个提醒。

调用图:调用 1 个内部函数(set_has_focus);被 1 处调用(show_quit_shortcut_hint);外部调用 1 个(now)。

ChatComposer::clear_quit_shortcut_hint1574–1578 ↗
fn clear_quit_shortcut_hint(&mut self, has_focus: bool)

作用:清除退出快捷键提醒,并把页脚恢复到正常活动后的模式。

数据流:输入焦点状态 → 清掉过期时间 → 重置页脚模式 → 更新焦点。

调用关系:外层取消退出提醒或用户继续操作时调用。

调用图:调用 2 个内部函数(set_has_focus, reset_mode_after_activity);被 1 处调用(clear_quit_shortcut_hint)。

ChatComposer::quit_shortcut_hint_visible1585–1589 ↗
fn quit_shortcut_hint_visible(&self) -> bool

作用:判断退出快捷键提醒现在是否还可见。过期后就算字段存在也不算可见。

数据流:读取提醒过期时间 → 和当前时间比较 → 返回是否仍在有效期内。

调用关系:页脚模式判断和快捷键处理会调用它。

调用图:被 3 处调用(quit_shortcut_hint_visible, footer_mode, handle_shortcut_overlay_key)。

ChatComposer::next_large_paste_placeholder1591–1613 ↗
fn next_large_paste_placeholder(&self, char_count: usize) -> String

作用:为一段大粘贴生成不会撞名的占位符。比如 [Pasted Content 5000 chars],重复时加 #2。

数据流:输入字符数 → 生成基础占位符 → 扫描现有 pending_pastes 找最大后缀 → 返回基础名或带新编号的名字。

调用关系:handle_paste 发现大段粘贴时调用它,保证每段粘贴都能单独对应真实内容。

调用图:被 1 处调用(handle_paste);外部调用 1 个(format!)。

ChatComposer::insert_str1615–1619 ↗
fn insert_str(&mut self, text: &str)

作用:向输入框插入普通字符串,并同步 bash 模式和弹窗。

数据流:输入字符串 → 插入文本框 → 根据文本更新 bash 模式 → 同步弹窗 → 不返回结果。

调用关系:普通粘贴、粘贴突发刷新和外部插入文本都会走它。

调用图:调用 2 个内部函数(sync_bash_mode_from_text, sync_popups);被 3 处调用(insert_str, handle_paste, handle_paste_burst_flush)。

ChatComposer::handle_key_event1622–1650 ↗
fn handle_key_event(&mut self, key_event: KeyEvent) -> (InputResult, bool)

作用:处理一个键盘事件,是输入框按键分发的总入口。它会根据是否有历史搜索或弹窗,把按键交给不同处理函数。

数据流:输入 KeyEvent → 忽略禁用输入和按键释放事件 → 优先处理历史搜索启动或进行中状态 → 根据当前弹窗类型分发 → 成功后修正 Vim 状态并同步弹窗 → 返回输入结果和是否消费按键。

调用关系:主输入循环调用它;它像交通警察,把每个键送到文件弹窗、技能弹窗、新版提及弹窗、斜杠弹窗或普通输入处理。

调用图:调用 6 个内部函数(handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup, handle_key_event_without_popup, reset_vim_mode_after_successful_dispatch, sync_popups);被 6 处调用(handle_key_event, press, type_chars_humanlike, handle_key_event, handle_key_event, input);外部调用 2 个(is_history_search_key, matches!)。

ChatComposer::popup_active1653–1655 ↗
fn popup_active(&self) -> bool

作用:判断当前是否有弹窗或历史搜索正在占用输入焦点。

数据流:检查 history_search 和 popups.active → 返回布尔值。

调用关系:外部决定能否打开编辑器、普通按键如何处理时会调用它。

调用图:调用 1 个内部函数(active);被 3 处调用(can_launch_external_editor, handle_key_event, is_normal_backtrack_mode)。

ChatComposer::clamp_to_char_boundary1658–1669 ↗
fn clamp_to_char_boundary(text: &str, pos: usize) -> usize

作用:把一个字节位置修正到合法字符边界。这样不会把中文、emoji 这类多字节字符切坏。

数据流:输入文本和字节位置 → 先限制不超过文本长度 → 如果落在字符中间,就退到前一个字符开头 → 返回安全位置。

调用关系:非 ASCII 输入和文件路径替换时使用它,防止字符串切片出错。

ChatComposer::handle_non_ascii_char1686–1758 ↗
fn handle_non_ascii_char(&mut self, input: KeyEvent, now: Instant) -> (InputResult, bool)

作用:处理中文、日文、emoji 等非 ASCII 字符输入。ASCII 可以理解为英文键盘常见字符;非 ASCII 往往来自输入法,行为更复杂。

数据流:输入按键和当前时间 → 若禁用粘贴突发就直接输入 → 否则尝试把快速连续输入识别成粘贴;必要时从文本框回收已插入前缀 → 最后普通插入并清理失效 pending_pastes → 返回处理结果。

调用关系:基础输入处理遇到非 ASCII 字符时调用它;它会在需要时把内容转交给 handle_paste。

调用图:调用 1 个内部函数(handle_paste);被 1 处调用(handle_input_basic_with_time);外部调用 2 个(clamp_to_char_boundary, unreachable!)。

ChatComposer::handle_key_event_with_file_popup1761–1871 ↗
fn handle_key_event_with_file_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool)

作用:在文件候选弹窗打开时处理按键。它支持上下移动、Esc 关闭、Tab/Enter 选择文件,图片文件会尽量变成附件。

数据流:输入按键 → 先处理快捷键覆盖层和 Esc 页脚提示 → 对上下键移动选择 → 对 Esc 记录已关闭 token 并关弹窗 → 对选择键插入路径或附加图片 → 其他键交给基础输入。

调用关系:handle_key_event 发现当前是文件弹窗时调用它;必要时会回到无弹窗处理来提交 Enter。

调用图:调用 7 个内部函数(attach_image, handle_input_basic, handle_key_event_without_popup, handle_shortcut_overlay_key, insert_selected_path, esc_hint_mode, reset_mode_after_activity);被 1 处调用(handle_key_event);外部调用 8 个(from, clamp_to_char_boundary, current_at_token, is_image_path, image_dimensions, debug!, trace!, unreachable!)。

ChatComposer::handle_key_event_with_skill_popup1874–1945 ↗
fn handle_key_event_with_skill_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool)

作用:在技能提及弹窗打开时处理按键。用户可以上下选择技能,按 Tab 或 Enter 插入。

数据流:输入按键 → 处理快捷键覆盖层并重置页脚提示 → 上下键移动候选 → Esc 关闭并记住被关闭的 token → Tab/Enter 保存选中提及并关闭 → 其他键走基础输入 → 关闭后插入选中提及。

调用关系:handle_key_event 在 Skill 弹窗状态下调用它;插入提及的具体工作交给 insert_selected_mention。

调用图:调用 5 个内部函数(current_mention_token, handle_input_basic, handle_shortcut_overlay_key, insert_selected_mention, reset_mode_after_activity);被 1 处调用(handle_key_event);外部调用 1 个(unreachable!)。

ChatComposer::handle_key_event_with_mentions_v2_popup1947–2060 ↗
fn handle_key_event_with_mentions_v2_popup(
        &mut self,
        key_event: KeyEvent,
    ) -> (InputResult, bool)

作用:在新版 @ 提及弹窗打开时处理按键。它既能选文件,也能选工具,还能用左右键切换搜索模式。

数据流:输入按键 → 处理快捷键覆盖层和页脚状态 → 判断是否能切换搜索模式 → 上下移动、左右切模式、Esc 关闭、Tab/Enter 选择 → 根据选择插入文件路径或工具提及;Enter 没选中时转成普通提交。

调用关系:handle_key_event 在 MentionV2 弹窗状态下调用它;它连接文件插入、提及插入和普通提交逻辑。

调用图:调用 8 个内部函数(current_editable_at_token, current_mentions_v2_token, handle_input_basic, handle_key_event_without_popup, handle_shortcut_overlay_key, insert_selected_file_path, insert_selected_mention, reset_mode_after_activity);被 1 处调用(handle_key_event);外部调用 1 个(unreachable!)。

ChatComposer::is_image_path2062–2069 ↗
fn is_image_path(path: &str) -> bool

作用:判断路径看起来是不是常见图片文件。它只看后缀名,比如 png、jpg、gif、webp。

数据流:输入路径字符串 → 转成小写 → 检查是否以支持的图片后缀结尾 → 返回布尔值。

调用关系:文件选择和新版提及插入文件时用它决定走图片附件流程还是普通路径流程。

ChatComposer::insert_selected_file_path2071–2108 ↗
fn insert_selected_file_path(&mut self, selected_path: &str)

作用:插入用户从新版提及弹窗里选中的文件路径。若是可读取的图片,就改为添加图片附件。

数据流:输入选中路径 → 如果像图片路径则尝试读取尺寸 → 成功时删除当前 token、附加图片并补空格;失败或非图片时插入普通路径。

调用关系:handle_key_event_with_mentions_v2_popup 选中文件时调用;它复用 attach_image 和 insert_selected_path。

调用图:调用 2 个内部函数(attach_image, insert_selected_path);被 1 处调用(handle_key_event_with_mentions_v2_popup);外部调用 6 个(from, clamp_to_char_boundary, is_image_path, image_dimensions, debug!, trace!)。

ChatComposer::trim_text_elements2110–2144 ↗
fn trim_text_elements(
        original: &str,
        trimmed: &str,
        elements: Vec<TextElement>,
    ) -> Vec<TextElement>

作用:当文本被去掉前后空白后,同步裁剪特殊文本元素的位置。否则元素范围会对不上新文本。

数据流:输入原文本、裁剪后文本和元素列表 → 计算裁剪掉的开头长度和新结尾 → 保留仍落在新文本里的元素并重算范围 → 返回新元素列表。

调用关系:作为文本提交或整理时的辅助工具,保证图片、提及等元素不会因为 trim 而错位。

调用图:外部调用 1 个(new)。

ChatComposer::expand_pending_pastes2147–2213 ↗
fn expand_pending_pastes(
        text: &str,
        mut elements: Vec<TextElement>,
        pending_pastes: &[(String, String)],
    ) -> (String, Vec<TextElement>)

作用:把大段粘贴占位符替换回真实内容,同时保留其他特殊元素的位置。它让提交或保存时拿到真正完整的文本。

数据流:输入当前文本、文本元素和 pending_pastes → 按占位符建立待替换队列 → 按元素顺序重建字符串 → 命中大粘贴占位符就写入真实内容并丢掉该元素,其他元素重新计算范围 → 返回新文本和新元素列表。

调用关系:current_text_with_pending、提交整理和目标草稿物化会调用它,是大粘贴从“占位显示”变回“真实内容”的关键步骤。

调用图:调用 1 个内部函数(new);被 3 处调用(text_with_pending, text_with_pending, materialize_goal_draft);外部调用 3 个(new, with_capacity, with_capacity)。

ChatComposer::skills2215–2217 ↗
fn skills(&self) -> Option<&Vec<SkillMetadata>>

作用:返回当前技能列表的只读引用。没有技能数据时返回 None。

数据流:读取 self.skills → 转成 Option<&Vec<SkillMetadata>> → 返回。

调用关系:外层查询技能候选状态时调用;它不修改任何内容。

调用图:被 1 处调用(skills)。

ChatComposer::plugins2219–2221 ↗
fn plugins(&self) -> Option<&Vec<PluginCapabilitySummary>>

作用:返回当前插件能力列表的只读引用。没有插件数据时返回 None。

数据流:读取 self.plugins → 转成 Option<&Vec<PluginCapabilitySummary>> → 返回。

调用关系:外层查询插件候选状态时调用;弹窗更新前后都可读取这份数据。

调用图:被 1 处调用(plugins)。

ChatComposer::mentions_enabled2223–2238 ↗
fn mentions_enabled(&self) -> bool

作用:判断“$提及”功能现在能不能用。只有系统里真的有技能、插件,或者已启用且有可用连接器时,才应该弹出提及候选。

数据流:它读取当前保存的技能列表、插件列表、连接器开关和连接器快照 → 分别检查这些来源里有没有东西可选 → 返回 true 或 false,不修改任何状态。

调用关系:它被 ChatComposer::current_mention_token 调用,像开关检查一样先挡在前面;如果这里说不能用,后面的“$”候选弹窗就不会出现。

调用图:被 1 处调用(current_mention_token)。

ChatComposer::current_prefixed_token_range2255–2358 ↗
fn current_prefixed_token_range(
        textarea: &TextArea,
        prefix: char,
        allow_empty: bool,
    ) -> Option<(Range<usize>, String)>

作用:找出光标附近以某个符号开头的词,比如“@文件名”或“$技能名”。它不只返回词本身,还返回这段词在整段文本里的位置,方便后面替换。

数据流:输入是文本框、前缀字符和是否允许空内容 → 它读取光标位置和文本,先把光标修到安全的字符边界,再向左右找空白分隔出的词 → 如果找到以指定符号开头的词,就输出这段范围和去掉前缀后的内容。

调用关系:这是很多提及和文件搜索功能的底层工具。ChatComposer::current_prefixed_token 会用它只取文字;ChatComposer::current_editable_at_token_with_options 会用它判断当前“@”能不能被编辑。

调用图:调用 2 个内部函数(cursor, text)。

ChatComposer::current_prefixed_token2360–2366 ↗
fn current_prefixed_token(
        textarea: &TextArea,
        prefix: char,
        allow_empty: bool,
    ) -> Option<String>

作用:简化版的前缀词查找器。调用者只关心“@”或“$”后面输入了什么,不关心它在文本里的位置时,就用它。

数据流:输入是文本框、前缀字符和是否允许空内容 → 它调用 ChatComposer::current_prefixed_token_range → 丢掉范围,只返回词内容,找不到就返回空。

调用关系:它包装了 ChatComposer::current_prefixed_token_range,被 ChatComposer::current_at_token 和 ChatComposer::current_mention_token 这类更具体的函数使用。

调用图:外部调用 1 个(current_prefixed_token_range)。

ChatComposer::current_at_token2371–2373 ↗
fn current_at_token(textarea: &TextArea) -> Option<String>

作用:专门找光标附近的“@xxx”文件查询词。这里不允许只有一个空的“@”,必须已经有查询内容。

数据流:输入是文本框 → 它用“@”作为前缀调用 ChatComposer::current_prefixed_token → 输出“@”后面的查询文字,或者没有可用 token 时输出空。

调用关系:它主要给命令弹窗同步逻辑和相关测试使用。ChatComposer::sync_command_popup 会靠它判断当前是不是应该让文件搜索优先于斜杠命令弹窗。

调用图:被 6 处调用(test_current_at_token_allows_file_queries_with_second_at, test_current_at_token_basic_cases, test_current_at_token_cursor_positions, test_current_at_token_ignores_mid_word_at, test_current_at_token_tracks_tokens_with_second_at, test_current_at_token_whitespace_boundaries);外部调用 1 个(current_prefixed_token)。

ChatComposer::current_editable_at_token_with_options2375–2406 ↗
fn current_editable_at_token_with_options(&self, allow_empty: bool) -> Option<String>

作用:判断当前光标处的“@提及”是否还是普通可编辑文字。已经变成原子元素的文件或提及,不应该再被当成正在输入的查询。

数据流:它读取输入框里的文本、光标和文本元素范围 → 找到当前“@”词后,检查这个范围是否已经绑定到某个元素;还会处理“@name”后面接特殊内容的情况 → 如果能编辑就返回查询词,否则返回空。

调用关系:它是 ChatComposer::current_editable_at_token 和 ChatComposer::current_mentions_v2_token 的共同入口,负责在弹文件搜索或新版提及时先排除已经插入好的元素。

调用图:调用 1 个内部函数(ends_plaintext_at_mention);被 2 处调用(current_editable_at_token, current_mentions_v2_token);外部调用 1 个(current_prefixed_token_range)。

ChatComposer::current_editable_at_token2408–2410 ↗
fn current_editable_at_token(&self) -> Option<String>

作用:取得当前正在编辑的“@文件查询”。这里不允许空查询,所以只有输入了“@a”这类内容时才返回。

数据流:它没有额外输入 → 调用 ChatComposer::current_editable_at_token_with_options,并指定不允许空 token → 输出查询词或空。

调用关系:它被 ChatComposer::handle_key_event_with_mentions_v2_popup 和 ChatComposer::sync_popups 使用,用来决定旧式文件搜索弹窗是否应该出现。

调用图:调用 1 个内部函数(current_editable_at_token_with_options);被 2 处调用(handle_key_event_with_mentions_v2_popup, sync_popups)。

ChatComposer::current_mentions_v2_token2412–2417 ↗
fn current_mentions_v2_token(&self) -> Option<String>

作用:取得新版“@提及”弹窗的当前查询词。新版功能允许用户刚打一个空的“@”就看到候选。

数据流:它先读取 mentions_v2_enabled 开关 → 如果没开,直接返回空;如果开了,就允许空 token 地查找当前“@”内容 → 返回查询词或空。

调用关系:它被 ChatComposer::handle_key_event_with_mentions_v2_popup、ChatComposer::on_file_search_result 和 ChatComposer::sync_popups 使用,是新版提及弹窗的入口判断。

调用图:调用 1 个内部函数(current_editable_at_token_with_options);被 3 处调用(handle_key_event_with_mentions_v2_popup, on_file_search_result, sync_popups)。

ChatComposer::current_mention_token2419–2424 ↗
fn current_mention_token(&self) -> Option<String>

作用:取得当前正在输入的“$技能/插件/应用”查询词。只有提及来源真的可用时,它才会返回内容。

数据流:它先调用 ChatComposer::mentions_enabled 检查功能是否可用 → 再在输入框里找“$”开头的词,并允许只有一个空的“$” → 输出查询词或空。

调用关系:它被 ChatComposer::handle_key_event_with_skill_popup 和 ChatComposer::sync_popups 使用,用来决定技能/插件提及弹窗是否显示。

调用图:调用 1 个内部函数(mentions_enabled);被 2 处调用(handle_key_event_with_skill_popup, sync_popups);外部调用 1 个(current_prefixed_token)。

ChatComposer::insert_selected_path2431–2471 ↗
fn insert_selected_path(&mut self, path: &str)

作用:把用户在文件搜索弹窗里选中的路径插入输入框,并替换掉正在输入的“@查询词”。如果路径里有空格,它会自动加引号,避免后续解析时被拆成多个参数。

数据流:输入是选中的路径字符串 → 它读取当前文本和光标,找出光标所在的那一整个词 → 用路径加一个空格替换这个词,并把光标移到插入内容之后。

调用关系:它被文件弹窗按键处理和 ChatComposer::insert_selected_file_path 调用,是“选中候选 → 写回输入框”的最后一步。

调用图:被 2 处调用(handle_key_event_with_file_popup, insert_selected_file_path);外部调用 2 个(clamp_to_char_boundary, format!)。

ChatComposer::insert_selected_mention2473–2517 ↗
fn insert_selected_mention(&mut self, insert_text: &str, path: Option<&str>)

作用:把选中的技能、插件、应用或新版提及插入输入框,并把它做成一个不可随便拆开的文本元素。这样系统提交时还能知道这个提及对应哪个真实路径。

数据流:输入是要显示的文本,以及可选路径 → 它找到并删除当前正在输入的 token → 插入一个原子元素,再把元素编号和提及绑定信息保存起来,最后补一个空格并移动光标。

调用关系:它被 ChatComposer::handle_key_event_with_mentions_v2_popup 和 ChatComposer::handle_key_event_with_skill_popup 调用,用在用户从候选弹窗确认选择时。它会借助 ChatComposer::mention_token_from_insert_text 识别可绑定的提及。

调用图:被 2 处调用(handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup);外部调用 2 个(clamp_to_char_boundary, mention_token_from_insert_text)。

ChatComposer::mention_token_from_insert_text2519–2537 ↗
fn mention_token_from_insert_text(insert_text: &str) -> Option<(char, String)>

作用:检查一段插入文本是不是合法提及,比如“$foo”或“@bar”。合法时,它会拆出前缀符号和名字。

数据流:输入是一段文本 → 它看第一个字符是不是“$”或“@”,再确认后面的名字不为空且只包含允许的字符 → 输出符号和名字,非法则返回空。

调用关系:它被 ChatComposer::insert_selected_mention 和 ChatComposer::current_mention_elements 使用,是判断文本元素能否成为提及绑定的统一规则。

调用图:外部调用 1 个(matches!)。

ChatComposer::current_mention_elements2539–2549 ↗
fn current_mention_elements(&self) -> Vec<(u64, char, String)>

作用:列出输入框里当前还存在的所有提及元素。它只关心真正插入成元素的“@”或“$”提及,不管普通文字。

数据流:它读取文本框的元素快照 → 对每个元素用 ChatComposer::mention_token_from_insert_text 检查 → 收集元素编号、符号和名字并返回列表。

调用关系:它被 ChatComposer::snapshot_mention_bindings 和 ChatComposer::take_mention_bindings 使用,用来从当前界面状态反推出还有效的提及。

调用图:被 2 处调用(snapshot_mention_bindings, take_mention_bindings)。

ChatComposer::snapshot_mention_bindings2551–2566 ↗
fn snapshot_mention_bindings(&self) -> Vec<MentionBinding>

作用:给当前输入框里的提及绑定拍一张快照。这样提交、保存历史或恢复草稿时,系统知道每个“$foo”或“@bar”实际指向哪里。

数据流:它先拿到当前仍存在的提及元素 → 对照 draft.mention_bindings 里的绑定表,确认编号、符号和名字都匹配 → 输出按文本顺序排列的 MentionBinding 列表。

调用关系:它被提交、清空、历史、草稿快照等流程调用,是把界面里的原子提及转换成可保存数据的出口。

调用图:调用 1 个内部函数(current_mention_elements);被 6 处调用(clear_for_ctrl_c, handle_submission_with_time, mention_bindings, prepare_submission_text_with_options, snapshot_draft, stage_slash_command_history_text);外部调用 1 个(new)。

ChatComposer::bind_mentions_from_snapshot2568–2604 ↗
fn bind_mentions_from_snapshot(&mut self, mention_bindings: Vec<MentionBinding>)

作用:从保存过的提及快照恢复输入框里的绑定关系。比如从历史记录恢复一条消息时,文字看起来一样,还要重新把提及和路径连起来。

数据流:输入是一组 MentionBinding → 它清空旧绑定,在当前文本里按顺序寻找对应的“$名字”或“@名字” → 找到后把那段文字标成元素,并记录元素编号到路径的绑定。

调用关系:它被 ChatComposer::set_text_content_with_mention_bindings 调用,是恢复草稿、历史或提交失败回滚时的重要补胶步骤。

调用图:调用 1 个内部函数(find_next_mention_token_range);被 1 处调用(set_text_content_with_mention_bindings);外部调用 1 个(format!)。

ChatComposer::plugin_at_mention_highlights2606–2619 ↗
fn plugin_at_mention_highlights(&self) -> Vec<(Range<usize>, Style)>

作用:找出应该用特殊颜色显示的插件“@提及”。这里插件路径以“plugin://”开头,并且文本看起来是“@xxx”。

数据流:它读取当前文本元素快照和提及绑定表 → 筛出绑定路径是插件、文本又以“@”开头的元素 → 输出这些范围和紫色样式。

调用关系:它被渲染函数 ChatComposer::render_with_mask_and_textarea_right_reserve 调用,让插件提及在输入框里有醒目的视觉提示。

调用图:被 1 处调用(render_with_mask_and_textarea_right_reserve)。

ChatComposer::prepare_submission_text2625–2634 ↗
fn prepare_submission_text(
        &mut self,
        record_history: bool,
    ) -> Option<(String, Vec<TextElement>)>

作用:用默认规则准备一次提交。默认会立刻检查斜杠命令,并展开粘贴占位符。

数据流:输入是是否记录历史 → 它调用 ChatComposer::prepare_submission_text_with_options,并传入默认的命令校验和粘贴处理方式 → 输出整理后的文本和文本元素,或因为不能提交而返回空。

调用关系:它被 ChatComposer::handle_submission_with_time 和 ChatComposer::prepare_inline_args_submission 调用,是大多数普通提交前的简便入口。

调用图:调用 1 个内部函数(prepare_submission_text_with_options);被 2 处调用(handle_submission_with_time, prepare_inline_args_submission)。

ChatComposer::prepare_submission_text_with_options2636–2734 ↗
fn prepare_submission_text_with_options(
        &mut self,
        record_history: bool,
        slash_validation: SlashValidation,
        pending_paste_handling: PendingPasteHandling,
    ) -> Opti

作用:真正执行“提交前整理”。它会清空输入框、展开粘贴、裁掉空白、检查未知命令、限制文本长度,并在失败时恢复原样。

数据流:输入是是否记历史、斜杠命令校验方式、粘贴占位符处理方式 → 它保存原始文本、元素、图片、提及和粘贴状态;然后整理文本并做校验 → 成功则记录历史并返回文本和元素,失败则发错误提示、恢复输入框并返回空。

调用关系:这是提交流程的核心。ChatComposer::prepare_submission_text 包装它,ChatComposer::handle_submission_with_time 在普通提交和排队提交时直接使用它。

调用图:调用 8 个内部函数(send, current_text, current_text_elements, set_text_content_with_mention_bindings, slash_input, snapshot_mention_bindings, user_input_too_large_message, record_local_submission);被 2 处调用(handle_submission_with_time, prepare_submission_text);外部调用 12 个(new, expand_pending_pastes, trim_text_elements, new, InsertHistoryCell, is_empty, local_image_paths, prune_local_images_for_submission, remote_image_urls, format! (+2 more))。

ChatComposer::handle_submission2738–2742 ↗
fn handle_submission(&mut self, should_queue: bool) -> (InputResult, bool)

作用:处理一次用户提交动作,比如按回车或提交快捷键。它用当前时间调用底层提交逻辑,并在成功后把 Vim 输入模式切回普通模式。

数据流:输入是是否应该排队 → 它生成当前时间,调用 ChatComposer::handle_submission_with_time → 再根据结果调用 ChatComposer::reset_vim_mode_after_successful_dispatch,最后返回提交结果和是否重绘。

调用关系:它被 ChatComposer::handle_key_event_without_popup 调用,是按键流程里进入提交处理的常用入口。

调用图:调用 2 个内部函数(handle_submission_with_time, reset_vim_mode_after_successful_dispatch);被 1 处调用(handle_key_event_without_popup);外部调用 1 个(now)。

ChatComposer::reset_vim_mode_after_successful_dispatch2744–2755 ↗
fn reset_vim_mode_after_successful_dispatch(&mut self, result: &InputResult)

作用:提交或命令成功发出后,把文本框切回 Vim 普通模式。Vim 模式是一种键盘编辑习惯,普通模式下按键多用于移动和操作,不是直接输入文字。

数据流:输入是一次 InputResult → 它判断结果是不是已提交、已排队、命令或带参数命令 → 如果是,就让文本框进入 Vim 普通模式;否则不动。

调用关系:它被 ChatComposer::handle_submission 和 ChatComposer::handle_key_event 调用,用来统一处理成功发送后的编辑状态。

调用图:被 2 处调用(handle_key_event, handle_submission);外部调用 1 个(matches!)。

ChatComposer::handle_submission_with_time2757–2894 ↗
fn handle_submission_with_time(
        &mut self,
        should_queue: bool,
        now: Instant,
    ) -> (InputResult, bool)

作用:这是提交动作的总调度器。它决定当前输入应该排队、执行斜杠命令、作为普通聊天提交,还是只是作为粘贴内容继续累积。

数据流:输入是是否排队和当前时间 → 它先处理粘贴爆发、斜杠命令、历史快照和输入恢复;如果是有效内容,就产出 Queued、Submitted、Command 等结果 → 过程中可能清空输入框、记录历史、或在失败时恢复原文本。

调用关系:它被 ChatComposer::handle_submission 调用,并把工作分给 ChatComposer::prepare_submission_text、ChatComposer::try_dispatch_bare_slash_command、ChatComposer::try_dispatch_slash_command_with_args 和粘贴处理函数。

调用图:调用 13 个内部函数(current_text, current_text_elements, handle_paste, prepare_submission_text, prepare_submission_text_with_options, set_text_content_with_mention_bindings, slash_commands_enabled, slash_input, snapshot_mention_bindings, try_dispatch_bare_slash_command (+3 more));被 1 处调用(handle_submission);外部调用 3 个(new, local_image_paths, matches!)。

ChatComposer::try_dispatch_bare_slash_command2898–2914 ↗
fn try_dispatch_bare_slash_command(&mut self) -> Option<InputResult>

作用:尝试执行只有命令名、没有参数的斜杠命令,比如“/clear”。即使命令弹窗已经不显示,它也能识别这种输入。

数据流:它读取当前输入框文本 → 让 slash_input 判断是不是完整的裸命令 → 如果命令当前不可用,就记录历史并返回不执行;否则清空输入框并输出对应命令结果。

调用关系:它被 ChatComposer::handle_submission_with_time 在普通文本提交前调用,优先把“/xxx”当命令而不是聊天内容。

调用图:调用 4 个内部函数(record_pending_slash_command_history, reject_slash_command_if_unavailable, slash_input, stage_slash_command_history);被 1 处调用(handle_submission_with_time);外部调用 2 个(Command, ServiceTierCommand)。

ChatComposer::try_dispatch_slash_command_with_args2918–2945 ↗
fn try_dispatch_slash_command_with_args(&mut self) -> Option<InputResult>

作用:尝试执行带参数的内联斜杠命令,比如“/review src/main.rs”。它会把命令后面的参数和参数里的特殊文本元素一起取出来。

数据流:它读取完整文本 → 解析出命令和剩余参数 → 如果命令不可用就提示并返回不执行;否则裁剪参数空白,保留参数范围内的文本元素,输出 CommandWithArgs。

调用关系:它被 ChatComposer::handle_submission_with_time 调用,在普通提交前检查当前输入是不是应当作为带参数命令执行。

调用图:调用 5 个内部函数(record_pending_slash_command_history, reject_slash_command_if_unavailable, slash_input, stage_slash_command_history, args_elements);被 1 处调用(handle_submission_with_time);外部调用 2 个(trim_text_elements, CommandWithArgs)。

ChatComposer::prepare_inline_args_submission2957–2968 ↗
fn prepare_inline_args_submission(
        &mut self,
        record_history: bool,
    ) -> Option<(String, Vec<TextElement>)>

作用:把一次输入准备成“只包含命令参数”的提交结果。适合先按普通提交流程整理文本,再从里面切出斜杠命令后的参数。

数据流:输入是是否记录历史 → 它先调用 ChatComposer::prepare_submission_text 得到整理后的完整文本和元素 → 再解析出参数部分,裁剪空白并同步裁剪文本元素 → 返回参数文本和参数元素。

调用关系:它调用自己所在模块的提交准备和 slash_input 的参数工具,服务于需要复用命令参数的流程。

调用图:调用 3 个内部函数(prepare_submission_text, args_elements, prepared_args);被 1 处调用(prepare_inline_args_submission);外部调用 1 个(trim_text_elements)。

ChatComposer::reject_slash_command_if_unavailable2970–2982 ↗
fn reject_slash_command_if_unavailable(&self, command: &SlashCommandItem) -> bool

作用:检查某个斜杠命令在当前任务运行时能不能用。不能用时,它会给用户插入一条错误提示。

数据流:输入是一个斜杠命令项 → 它读取当前是否有任务正在运行,并询问命令是否允许任务中执行 → 如果不允许,就发送错误历史消息并返回 true;否则返回 false。

调用关系:它被 ChatComposer::try_dispatch_bare_slash_command 和 ChatComposer::try_dispatch_slash_command_with_args 调用,是命令真正执行前的门禁。

调用图:调用 2 个内部函数(send, available_during_task);被 2 处调用(try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args);外部调用 4 个(new, InsertHistoryCell, format!, new_error_event)。

ChatComposer::stage_slash_command_history2989–2994 ↗
fn stage_slash_command_history(&mut self, command: &SlashCommandItem)

作用:暂存即将执行的斜杠命令到历史记录。例外是“/clear”,因为清屏命令不应该再被当作可回放历史。

数据流:输入是命令项 → 如果是 Clear 就直接跳过;否则取当前输入文本并裁掉首尾空白 → 交给 ChatComposer::stage_slash_command_history_text 保存。

调用关系:它被裸命令和带参数命令派发流程调用,确保命令执行前先有机会进入历史。

调用图:调用 1 个内部函数(stage_slash_command_history_text);被 2 处调用(try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args);外部调用 1 个(matches!)。

ChatComposer::stage_selected_slash_command_history3000–3005 ↗
fn stage_selected_slash_command_history(&mut self, command: &CommandItem)

作用:当用户从命令候选里选了一个命令时,把这个选择暂存到历史。它同样跳过“/clear”。

数据流:输入是候选命令项 → 如果不是 Clear,就拼出“/命令名”文本 → 调用 ChatComposer::stage_slash_command_history_text 保存。

调用关系:它和 ChatComposer::stage_slash_command_history 使用同一个保存入口,只是来源是“弹窗选中”而不是“文本框里已有命令”。

调用图:调用 1 个内部函数(stage_slash_command_history_text);外部调用 2 个(format!, matches!)。

ChatComposer::stage_slash_command_history_text3012–3021 ↗
fn stage_slash_command_history_text(&mut self, text: String)

作用:把一条斜杠命令文本连同当前附件、文本元素、提及绑定和粘贴占位符一起暂存为历史条目。

数据流:输入是一段命令文本 → 它读取当前文本元素、图片路径、远程图片、提及绑定和待处理粘贴 → 组装成 HistoryEntry,放进 pending_slash_command_history。

调用关系:它被 ChatComposer::stage_slash_command_history 和 ChatComposer::stage_selected_slash_command_history 调用,是斜杠命令历史暂存的实际落点。

调用图:调用 1 个内部函数(snapshot_mention_bindings);被 2 处调用(stage_selected_slash_command_history, stage_slash_command_history);外部调用 2 个(local_image_paths, remote_image_urls)。

ChatComposer::handle_remote_image_selection_key3023–3029 ↗
fn handle_remote_image_selection_key(
        &mut self,
        key_event: &KeyEvent,
    ) -> Option<(InputResult, bool)>

作用:把按键交给远程图片选择逻辑处理。比如用户正在选择一张远程图片时,方向键或确认键不应该被普通输入框抢走。

数据流:输入是按键事件 → 它把按键和文本框交给 attachments 子系统 → 如果图片选择逻辑处理了,就返回输入结果和是否重绘;否则返回空。

调用关系:它被 ChatComposer::handle_key_event_without_popup 最先调用,优先处理图片选择这种特殊状态。

调用图:被 1 处调用(handle_key_event_without_popup);外部调用 1 个(handle_remote_image_selection_key)。

ChatComposer::handle_key_event_without_popup3032–3162 ↗
fn handle_key_event_without_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool)

作用:处理没有弹窗抢焦点时的普通按键。它决定按键是提交、排队、历史上下翻、Vim 操作、bash 模式切换,还是普通输入。

数据流:输入是一个按键事件 → 它依次检查远程图片、快捷键提示、bash 模式、Vim 模式、退出提示、提交键、Ctrl-D、历史导航等情况 → 最后要么返回对应结果,要么把按键交给 ChatComposer::handle_input_basic。

调用关系:它被总的按键处理以及文件、新版提及弹窗流程调用,是“没有弹窗处理这个键时”的兜底处理器。

调用图:调用 16 个内部函数(apply_history_entry, current_text, handle_input_basic, handle_paste, handle_remote_image_selection_key, handle_shortcut_overlay_key, handle_submission, history_navigation_cursor, is_bang_shell_command, is_empty (+6 more));被 3 处调用(handle_key_event, handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup);外部调用 2 个(clear_remote_image_selection, matches!)。

ChatComposer::is_bang_shell_command3164–3166 ↗
fn is_bang_shell_command(&self) -> bool

作用:判断当前输入是不是以感叹号开头的 shell 命令。这里的 shell 命令可以理解成让系统执行终端命令,而不是普通聊天。

数据流:它读取当前文本 → 去掉开头空白后检查第一个字符是不是“!” → 返回 true 或 false。

调用关系:它被 ChatComposer::handle_key_event_without_popup 用来决定排队提交规则,也被 ChatComposer::shell_mode_footer_line 用来显示底部提示。

调用图:调用 1 个内部函数(current_text);被 2 处调用(handle_key_event_without_popup, shell_mode_footer_line)。

ChatComposer::handle_paste_burst_flush3182–3194 ↗
fn handle_paste_burst_flush(&mut self, now: Instant) -> bool

作用:检查快速输入缓冲区是否该结算。这里的“粘贴爆发”是指很多字符在极短时间内到达,程序会把它当作粘贴而不是逐字输入。

数据流:输入是当前时间 → 它询问 paste_burst 是否到期;如果结果是粘贴,就调用 ChatComposer::handle_paste;如果只是单个字符,就插入该字符 → 返回是否处理了内容。

调用关系:它被 ChatComposer::flush_paste_burst_if_due 和 ChatComposer::handle_input_basic_with_time 调用,防止缓冲的粘贴内容一直卡着不进输入框。

调用图:调用 2 个内部函数(handle_paste, insert_str);被 2 处调用(flush_paste_burst_if_due, handle_input_basic_with_time)。

ChatComposer::handle_input_basic3210–3218 ↗
fn handle_input_basic(&mut self, input: KeyEvent) -> (InputResult, bool)

作用:处理最普通的一次按键输入,并忽略按键释放事件,避免同一个字符被重复算两次。

数据流:输入是按键事件 → 如果不是按下或重复按下,就返回不处理;否则带上当前时间调用 ChatComposer::handle_input_basic_with_time → 返回输入结果和是否重绘。

调用关系:它被多个按键处理路径调用,包括无弹窗、文件弹窗、新版提及弹窗和技能弹窗的兜底输入处理。

调用图:调用 1 个内部函数(handle_input_basic_with_time);被 4 处调用(handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup, handle_key_event_without_popup);外部调用 2 个(now, matches!)。

ChatComposer::handle_input_basic_with_time3220–3363 ↗
fn handle_input_basic_with_time(
        &mut self,
        input: KeyEvent,
        now: Instant,
    ) -> (InputResult, bool)

作用:真正处理普通编辑按键。它既要支持正常打字,又要识别快速粘贴、处理特殊元素删除、维护 bash 模式和底部提示状态。

数据流:输入是按键和当前时间 → 它先冲刷过期粘贴缓冲,再判断字符是否该进入粘贴爆发缓冲;对非字符或需要立即处理的键,交给文本框执行 → 最后同步 bash 模式、清理被删元素关联,并返回需要重绘。

调用关系:它由 ChatComposer::handle_input_basic 调用,并把粘贴交给 ChatComposer::handle_paste,把非 ASCII 字符交给 ChatComposer::handle_non_ascii_char,把删除后的清理交给 ChatComposer::reconcile_deleted_elements。

调用图:调用 7 个内部函数(handle_non_ascii_char, handle_paste, handle_paste_burst_flush, reconcile_deleted_elements, sync_bash_mode_from_text, reset_mode_after_activity, has_ctrl_or_alt);被 1 处调用(handle_input_basic);外部调用 3 个(clamp_to_char_boundary, is_empty, matches!)。

ChatComposer::sync_bash_mode_from_text3365–3370 ↗
fn sync_bash_mode_from_text(&mut self)

作用:根据用户输入自动切换 bash 模式。如果用户在开头打了“!”,它会把这个符号拿掉,并把草稿标记成 bash 模式。

数据流:它读取当前 bash 模式和文本内容 → 如果还不是 bash 模式且文本以“!”开头,就删除第一个字符并打开 bash 模式 → 没有返回值,只改内部状态。

调用关系:它被 ChatComposer::handle_input_basic_with_time 和 ChatComposer::insert_str 调用,保证键盘输入和程序插入文字后都能同步模式。

调用图:被 2 处调用(handle_input_basic_with_time, insert_str)。

ChatComposer::reconcile_deleted_elements3372–3385 ↗
fn reconcile_deleted_elements(&mut self, elements_before: Vec<String>)

作用:清理用户删掉的原子元素对应的后台记录。比如删除了一个粘贴占位符或图片占位符,就不能再在提交时带上它。

数据流:输入是编辑前的元素载荷列表 → 它对比编辑后的元素载荷,找出已经消失的项 → 从待处理粘贴里删除对应占位符,并让附件系统移除已删的本地占位符。

调用关系:它被 ChatComposer::handle_input_basic_with_time 在文本框处理按键后调用,专门处理“元素被用户删掉了,后台也要跟着删”的收尾工作。

调用图:被 1 处调用(handle_input_basic_with_time);外部调用 1 个(remove_deleted_local_placeholders)。

ChatComposer::handle_shortcut_overlay_key3393–3414 ↗
fn handle_shortcut_overlay_key(&mut self, key_event: &KeyEvent) -> bool

作用:处理快捷键说明面板的开关按键。只有输入框为空、没有粘贴爆发时,才允许切换这个提示面板。

数据流:输入是按键事件 → 它先确认是按下事件,再检查是否匹配切换快捷键、输入是否为空、是否不在粘贴中 → 如果满足,就计算下一个底部模式并更新,返回是否发生变化。

调用关系:它被无弹窗、文件弹窗、新版提及弹窗和技能弹窗的按键流程调用,优先响应“显示/隐藏快捷键帮助”。

调用图:调用 4 个内部函数(is_empty, is_in_paste_burst, quit_shortcut_hint_visible, toggle_shortcut_mode);被 4 处调用(handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup, handle_key_event_without_popup)。

ChatComposer::sync_popups3499–3589 ↗
fn sync_popups(&mut self)

作用:统一同步所有输入弹窗:斜杠命令、文件搜索、技能/插件提及和新版提及。它会根据当前光标和文本决定哪个弹窗该出现,哪个该关闭。

数据流:它读取历史搜索、弹窗开关、当前“@”和“$”token、历史导航状态、斜杠命令开关等 → 先排除不该弹的情况,再按优先级同步命令、新版提及、技能提及、文件搜索 → 过程中可能发送文件搜索事件、更新当前弹窗和清除 dismissed 标记。

调用关系:它被历史响应、绘制前 tick、外部编辑、按键处理、粘贴、插入文字和移动光标等流程调用,是输入框状态变化后刷新弹窗的总入口。

调用图:调用 13 个内部函数(send, current_editable_at_token, current_mention_token, current_mentions_v2_token, current_text, history_navigation_cursor, popups_enabled, slash_commands_enabled, sync_command_popup, sync_file_search_popup (+3 more));被 17 处调用(on_history_entry_response, pre_draw_tick_at, apply_external_edit, handle_key_event, handle_paste, insert_str, move_cursor_to_end, move_cursor_to_history_entry_end, restore_draft, set_connector_mentions (+7 more));外部调用 3 个(new, StartFileSearch, matches!)。

ChatComposer::sync_command_popup3594–3644 ↗
fn sync_command_popup(&mut self, allow: bool)

作用:同步斜杠命令候选弹窗。只有光标在第一行的“/命令名”位置编辑时,它才显示或更新候选。

数据流:输入是是否允许显示命令弹窗 → 如果不允许就关闭已有命令弹窗;如果允许,它读取第一行文本和光标,判断是否正在编辑命令名,并生成过滤文字 → 更新已有弹窗或新建弹窗;如果光标在“@”文件 token 上,则让文件搜索优先。

调用关系:它只由 ChatComposer::sync_popups 调用,是总弹窗同步流程中的斜杠命令分支。

调用图:调用 1 个内部函数(slash_input);被 1 处调用(sync_popups);外部调用 3 个(current_at_token, matches!, Command)。

ChatComposer::sync_file_search_popup3647–3685 ↗
fn sync_file_search_popup(&mut self, query: String)

作用:同步文件搜索弹窗,并通知后台开始按当前查询搜索文件。用户输入“@xxx”时,这里负责让候选列表跟着变。

数据流:输入是文件查询字符串 → 如果这个查询刚被用户关闭过,就不再弹;否则发送 StartFileSearch 事件,更新或创建 FileSearchPopup,并记录当前查询 → 空查询会显示空提示并清掉 current_file_query。

调用关系:它由 ChatComposer::sync_popups 在识别到可编辑“@文件查询”时调用,把输入框查询词传给文件搜索 UI 和后台搜索事件。

调用图:调用 2 个内部函数(send, new);被 1 处调用(sync_popups);外部调用 3 个(new, StartFileSearch, File)。

ChatComposer::sync_mention_popup3687–3709 ↗
fn sync_mention_popup(&mut self, query: String)

作用:同步旧式“$技能/插件/应用”提及弹窗。它会用当前查询过滤可选项。

数据流:输入是查询字符串 → 如果这个查询已被关闭过就跳过;否则调用 ChatComposer::mention_items 生成候选 → 没候选就关闭弹窗,有候选就更新或新建 SkillPopup。

调用关系:它由 ChatComposer::sync_popups 在识别到“$”提及时调用,是旧式提及候选列表的刷新点。

调用图:调用 2 个内部函数(mention_items, new);被 1 处调用(sync_popups);外部调用 1 个(Skill)。

ChatComposer::sync_mentions_v2_popup3711–3744 ↗
fn sync_mentions_v2_popup(&mut self, query: String)

作用:同步新版“@提及”弹窗。新版弹窗会同时包含文件搜索结果以及技能、插件等候选目录。

数据流:输入是查询字符串 → 它按查询发送 StartFileSearch 事件,并记录当前文件查询;然后从技能和插件构建搜索目录 → 更新或创建 MentionV2Popup,并清掉 dismissed 标记。

调用关系:它由 ChatComposer::sync_popups 在新版提及开启且当前有“@”token 时调用,连接了文件搜索和新版提及候选 UI。

调用图:调用 1 个内部函数(send);被 1 处调用(sync_popups);外部调用 5 个(new, new, StartFileSearch, build_search_catalog, MentionV2)。

ChatComposer::mention_items3746–3847 ↗
fn mention_items(&self) -> Vec<MentionItem>

作用:生成“$提及”弹窗里能选的所有项目,包括技能、插件和应用连接器。每个项目都包含显示名、说明、插入文本、搜索词和真实路径。

数据流:它读取技能列表、插件列表、连接器开关和连接器快照 → 对每类来源整理显示名、描述、搜索关键词、插入文本和排序等级 → 输出 MentionItem 列表。

调用关系:它被 ChatComposer::sync_mention_popup 调用,是旧式提及弹窗的数据来源。它还会借助连接器元数据和本文件里的描述提取函数补全应用说明。

调用图:调用 4 个内部函数(connector_display_label, connector_mention_slug, skill_description, skill_display_name);被 1 处调用(sync_mention_popup);外部调用 4 个(connector_brief_description, new, format!, vec!)。

ChatComposer::connector_brief_description3849–3851 ↗
fn connector_brief_description(connector: &AppInfo) -> String

作用:给应用连接器取一个简短说明。没有说明时返回空字符串,方便界面直接显示。

数据流:输入是 AppInfo → 它调用 ChatComposer::connector_description 尝试取描述 → 有就返回描述,没有就返回空字符串。

调用关系:它被 ChatComposer::mention_items 调用,用来给应用类提及候选填充说明文字。

调用图:外部调用 1 个(connector_description)。

ChatComposer::connector_description3853–3860 ↗
fn connector_description(connector: &AppInfo) -> Option<String>

作用:从连接器信息里取出干净的描述文本。空白描述会被当成没有描述。

数据流:输入是 AppInfo → 它读取 description 字段,去掉首尾空白,并过滤掉空字符串 → 返回描述文本或空。

调用关系:它被 ChatComposer::connector_brief_description 调用,是连接器说明清洗的底层小工具。

ChatComposer::set_has_focus3862–3864 ↗
fn set_has_focus(&mut self, has_focus: bool)

作用:记录输入框现在有没有焦点。焦点可以理解成键盘输入当前是不是归这个输入框接收。

数据流:输入是 true 或 false → 它把 has_focus 字段改成对应值 → 没有返回值。

调用关系:它被 ChatComposer::clear_quit_shortcut_hint 和 ChatComposer::show_quit_shortcut_hint 调用,用于配合退出快捷键提示的显示和隐藏。

调用图:被 2 处调用(clear_quit_shortcut_hint, show_quit_shortcut_hint)。

ChatComposer::set_input_enabled3867–3875 ↗
fn set_input_enabled(&mut self, enabled: bool, placeholder: Option<String>)

作用:打开或关闭输入框。关闭时可以设置占位提示,并且会关掉还开着的交互弹窗,避免用户在不能输入时还操作候选列表。

数据流:输入是是否启用和可选占位文字 → 它更新 draft.input_enabled 和禁用提示;如果是禁用且当前有弹窗,就把活动弹窗设为 None → 没有返回值。

调用关系:它被外层的 ChatComposer::set_composer_input_enabled 和 ChatComposer::show_shutdown_in_progress 调用,用在全局需要禁止输入的场景,比如关闭流程中。

调用图:调用 1 个内部函数(active);被 2 处调用(set_composer_input_enabled, show_shutdown_in_progress)。

ChatComposer::show_shutdown_in_progress3877–3884 ↗
fn show_shutdown_in_progress(&mut self)

作用:在程序正在退出时,把输入框切到“不能再输入”的状态,并显示“Shutting down...”之类的提示。这样用户不会误以为还能继续提交新消息。

数据流:进入时不需要外部参数,只读取并修改当前输入框和页脚状态 → 它关闭输入、清掉退出快捷键倒计时、清空页脚提示和闪烁消息 → 出来后界面会显示为关机中,输入区域不可用。

调用关系:它会调用 set_input_enabled 来真正关闭输入并设置占位提示;当外层流程检测到应用进入关闭阶段时,会调用这个方法让底部输入区同步变成安全的只读状态。

调用图:调用 1 个内部函数(set_input_enabled);被 1 处调用(show_shutdown_in_progress);外部调用 1 个(new)。

ChatComposer::set_task_running3886–3888 ↗
fn set_task_running(&mut self, running: bool)

作用:告诉输入框:后台任务现在是不是正在运行。这个状态会影响页脚提示,比如是否显示“可以排队提交”的提示。

数据流:进入一个布尔值 running → 直接写到 is_task_running 字段 → 出来后后续渲染会按这个状态决定显示哪些提示。

调用关系:外层任务调度或会话状态变化时会调用它;渲染页脚时会读取这个标记,决定空闲状态和运行状态下的提示差异。

调用图:被 1 处调用(set_task_running)。

ChatComposer::set_queue_submissions3890–3892 ↗
fn set_queue_submissions(&mut self, queue_submissions: bool)

作用:设置是否允许把用户的新提交先排队等后台任务结束。它让界面能正确告诉用户“现在发的消息会不会排队”。

数据流:进入一个布尔值 queue_submissions → 写入 composer 的 queue_submissions 字段 → 出来后输入处理和页脚显示会按这个开关行动。

调用关系:外层配置或运行状态变化时会调用它;测试里也用它制造“允许排队”的场景,检查页脚提示是否正确。

调用图:被 1 处调用(set_queue_submissions)。

ChatComposer::set_context_window3894–3902 ↗
fn set_context_window(&mut self, percent: Option<i64>, used_tokens: Option<i64>)

作用:更新上下文窗口用量,也就是模型还能记住多少内容的大致进度。它避免重复设置相同值,减少没必要的重画。

数据流:进入两个可选数字:百分比和已用 token 数,token 可以理解成模型处理文字时用的小单位 → 如果和当前页脚里的一样就什么都不做,否则写入新值 → 出来后页脚能显示新的上下文用量。

调用关系:会话状态或模型统计更新时会调用它;render_with_mask_and_textarea_right_reserve 之后会把这些数据融入右侧状态信息。

调用图:被 1 处调用(set_context_window)。

ChatComposer::set_esc_backtrack_hint3904–3911 ↗
fn set_esc_backtrack_hint(&mut self, show: bool)

作用:控制是否显示 Esc 返回提示。比如用户可以按 Esc 回到上一步时,页脚需要提醒他。

数据流:进入 show 表示要不要显示 → 写入 footer.esc_backtrack_hint;如果要显示,就用 esc_hint_mode 把页脚切到 Esc 提示模式;如果不显示,就用 reset_mode_after_activity 恢复普通模式 → 出来后页脚模式随之改变。

调用关系:show_esc_backtrack_hint 和 clear_esc_backtrack_hint 这类外层动作会用它;它把具体模式切换交给 esc_hint_mode 和 reset_mode_after_activity,保证任务运行中和空闲时提示不混乱。

调用图:调用 2 个内部函数(esc_hint_mode, reset_mode_after_activity);被 2 处调用(clear_esc_backtrack_hint, show_esc_backtrack_hint)。

ChatComposer::set_status_line3913–3919 ↗
fn set_status_line(&mut self, status_line: Option<Line<'static>>) -> bool

作用:设置底部状态行显示的文字,比如模型名、目录、上下文用量等。返回值告诉调用者界面是否真的变了。

数据流:进入一个可选的 Line,Line 是终端界面里带样式的一行文字 → 如果和旧值一样,返回 false;否则保存新状态行并返回 true → 出来后调用者可据此决定是否重画。

调用关系:外层状态栏更新时会调用它;渲染函数会读取 footer.status_line_value,把它画到页脚里。

调用图:被 1 处调用(set_status_line)。

ChatComposer::set_status_line_enabled3929–3935 ↗
fn set_status_line_enabled(&mut self, enabled: bool) -> bool

作用:打开或关闭底部状态行。这样状态行可以按需要出现,而不是一直占据提示区域。

数据流:进入 enabled 布尔值 → 若没变化返回 false;有变化就写入 footer.status_line_enabled 并返回 true → 后续渲染会按这个开关决定是否使用状态行布局。

调用关系:外层配置、模式切换或测试会调用它;渲染页脚时会读取这个开关,决定显示普通快捷键提示还是状态行。

调用图:被 1 处调用(set_status_line_enabled)。

ChatComposer::set_side_conversation_context_label3937–3943 ↗
fn set_side_conversation_context_label(&mut self, label: Option<String>) -> bool

作用:设置右侧显示的“旁路对话”上下文标签。它让用户知道当前输入可能属于哪个额外上下文。

数据流:进入一个可选标签字符串 → 如果没变返回 false;变了就保存并返回 true → 出来后页脚右侧可能显示这个标签。

调用关系:外层在切换旁路对话上下文时调用;渲染页脚时它的优先级高于一些普通右侧信息。

调用图:被 1 处调用(set_side_conversation_context_label)。

ChatComposer::set_active_agent_label3950–3956 ↗
fn set_active_agent_label(&mut self, active_agent_label: Option<String>) -> bool

作用:设置当前活跃 agent 的标签,agent 可以理解成正在帮你工作的某个助手角色或执行者。这个标签用于让用户知道现在是谁在处理。

数据流:进入一个可选字符串 active_agent_label → 如果和旧值一样返回 false;否则写入页脚并返回 true → 后续页脚状态可以显示新的活跃 agent 信息。

调用关系:外层 agent 状态变化时会调用它;渲染页脚时会把这个信息纳入 footer_props 后显示。

调用图:被 1 处调用(set_active_agent_label)。

skill_description3976–3985 ↗
fn skill_description(skill: &SkillMetadata) -> Option<String>

作用:为一个技能挑出最适合展示的简短说明。它会优先用接口里的短说明,没有再用技能自己的短说明,最后才用完整说明。

数据流:进入 SkillMetadata,也就是技能的元信息 → 按优先级取说明文字,去掉首尾空白 → 如果空了返回 None,否则返回整理后的字符串。

调用关系:mention_items 会调用它来准备技能提及列表里的说明文字,让弹窗里显示的内容尽量短而清楚。

调用图:被 1 处调用(mention_items)。

is_mention_name_char3987–3989 ↗
fn is_mention_name_char(byte: u8) -> bool

作用:判断一个字节能不能算作提及名称的一部分。提及名称这里只允许英文字母、数字、下划线和短横线。

数据流:进入一个 u8 字节 → 检查它是否落在允许的字符范围里 → 返回 true 或 false。

调用关系:它是提及识别的小工具;find_next_mention_token_range 在判断提及结尾时会间接用到类似规则,避免把无关字符算进名字。

调用图:外部调用 1 个(matches!)。

ends_plaintext_at_mention3991–4004 ↗
fn ends_plaintext_at_mention(bytes: &[u8], index: usize) -> bool

作用:判断一个 @ 提及到某个位置时,后面是不是已经合理结束。它防止把路径、邮箱或更长名字的一部分误认成一个完整提及。

数据流:进入整段文本的字节数组和候选结束位置 → 查看结束位置后面的字符,是空白、合适的标点或文本结尾才算结束 → 返回是否可以在这里截断提及。

调用关系:current_editable_at_token_with_options 和 find_next_mention_token_range 会用它;尤其在恢复历史里的 @ 提及时,它帮助判断边界,避免把 @sample/pkg 误绑成 @sample。

调用图:被 2 处调用(current_editable_at_token_with_options, find_next_mention_token_range)。

starts_plaintext_at_mention4006–4014 ↗
fn starts_plaintext_at_mention(text: &str, index: usize) -> bool

作用:判断某个 @ 提及的开头是不是一个真正的文本开头。它主要用来避免把邮箱地址中间的 @ 当成提及。

数据流:进入整段文本和候选起始位置 → 如果在开头就允许;否则看前一个字符是不是空白或不是名称字符 → 返回这个位置是否适合作为提及开头。

调用关系:find_next_mention_token_range 会调用它;它又使用 is_mention_name_char_char 判断前一个字符是不是名字的一部分。

调用图:被 1 处调用(find_next_mention_token_range)。

is_mention_name_char_char4016–4018 ↗
fn is_mention_name_char_char(ch: char) -> bool

作用:判断一个字符能不能算作提及名字的一部分。它和字节版规则一样,只接受 ASCII 字母数字、下划线和短横线。

数据流:进入一个 char 字符 → 检查是否是 ASCII 字母数字或 '_'、'-' → 返回 true 或 false。

调用关系:starts_plaintext_at_mention 用它来检查 @ 前面的字符,从而判断这个 @ 是不是独立提及的开始。

调用图:外部调用 1 个(matches!)。

find_next_mention_token_range4020–4069 ↗
fn find_next_mention_token_range(text: &str, token: &str, from: usize) -> Option<Range<usize>>

作用:在一段文字里,从指定位置开始寻找下一个完整的提及 token,并返回它的位置范围。它的重点是“完整”,不是看到相同字符串就算。

数据流:进入文本、要找的 token、起始下标 → 按字节向后扫描,先找同样的开头符号,再比对完整 token;如果是 @,还会检查前后边界,避开邮箱和路径片段 → 找到就返回 Range,找不到返回 None。

调用关系:bind_mentions_from_snapshot 会用它把保存过的提及绑定重新贴回文本;它调用 starts_plaintext_at_mention 和 ends_plaintext_at_mention 保证恢复绑定时不会绑错位置。

调用图:调用 2 个内部函数(ends_plaintext_at_mention, starts_plaintext_at_mention);被 1 处调用(bind_mentions_from_snapshot)。

ChatComposer::cursor_pos4072–4074 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:计算光标在屏幕上的位置。外层渲染系统需要这个位置,才能把终端光标放到用户正在编辑的地方。

数据流:进入一个 Rect,Rect 是屏幕上的矩形区域 → 用默认的右侧预留宽度 0 调用更通用的计算函数 → 返回可选的 x、y 坐标;如果不该显示光标则可能返回 None。

调用关系:这是给界面框架调用的简单入口;真正计算交给 cursor_pos_with_textarea_right_reserve。

调用图:调用 1 个内部函数(cursor_pos_with_textarea_right_reserve);被 2 处调用(cursor_pos, cursor_pos)。

ChatComposer::cursor_style4076–4082 ↗
fn cursor_style(&self, _area: Rect) -> crossterm::cursor::SetCursorStyle

作用:决定终端光标长什么样。Vim 插入模式下用竖条光标,否则使用用户默认的光标形状。

数据流:进入区域参数但这里不使用 → 读取 textarea 是否使用 Vim 插入光标 → 返回 crossterm 的光标样式命令。

调用关系:渲染系统在设置光标时调用它;它只读取文本框状态,不改动 composer。

调用图:被 1 处调用(cursor_style)。

ChatComposer::desired_height4084–4086 ↗
fn desired_height(&self, width: u16) -> u16

作用:告诉布局系统这个输入框希望占几行高。没有它,界面就很容易把输入区或页脚挤坏。

数据流:进入可用宽度 → 用右侧预留宽度 0 调用更通用的高度计算 → 返回需要的行数。

调用关系:input_height、notes_input_height 和外层 desired_height 会调用它;实际计算交给 desired_height_with_textarea_right_reserve。

调用图:调用 1 个内部函数(desired_height_with_textarea_right_reserve);被 3 处调用(input_height, notes_input_height, desired_height)。

ChatComposer::render4088–4090 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把聊天输入框画到终端缓冲区里。它是默认渲染入口,不对输入内容做遮罩。

数据流:进入要画的区域和 Buffer,Buffer 可以理解成终端屏幕草稿纸 → 调用 render_with_mask,遮罩字符传 None → 输出体现在 Buffer 被写入了输入框、页脚、提示等内容。

调用关系:render_input、render_ref 和测试辅助函数会调用它;真正复杂绘制交给 render_with_mask。

调用图:调用 1 个内部函数(render_with_mask);被 3 处调用(plugin_mention_foreground_color, render_input, render_ref)。

ChatComposer::desired_height_with_textarea_right_reserve4094–4126 ↗
fn desired_height_with_textarea_right_reserve(
        &self,
        width: u16,
        textarea_right_reserve: u16,
    ) -> u16

作用:计算输入区在给定宽度下到底要多高,并允许给文本框右边预留一些空间。它把文本、附件、页脚、弹窗这些高度都算进去。

数据流:进入总宽度和右侧预留列数 → 先拿页脚属性,算页脚高度和空行;再算文本框实际宽度、远程图片附件行数;最后根据当前有没有命令/文件/技能/提及弹窗,加上弹窗需要的高度 → 返回总高度。

调用关系:desired_height 会调用它;当外层布局需要知道底部面板占多少行时,它会综合 footer_props、custom_footer_height、附件和 active popup 的高度。

调用图:调用 2 个内部函数(custom_footer_height, footer_props);被 2 处调用(desired_height, desired_height);外部调用 3 个(footer_spacing, remote_image_lines, from)。

ChatComposer::render_with_mask4130–4134 ↗
fn render_with_mask(&self, area: Rect, buf: &mut Buffer, mask_char: Option<char>)

作用:带可选遮罩地渲染输入框。遮罩字符常用于不想直接显示真实输入内容的场景。

数据流:进入区域、Buffer 和可选 mask_char → 用默认右侧预留宽度 0 调用更通用的渲染函数 → Buffer 被写入最终界面。

调用关系:render 和 render_input 会调用它;具体绘制交给 render_with_mask_and_textarea_right_reserve。

调用图:调用 1 个内部函数(render_with_mask_and_textarea_right_reserve);被 2 处调用(render, render_input)。

ChatComposer::render_with_mask_and_textarea_right_reserve4136–4462 ↗
fn render_with_mask_and_textarea_right_reserve(
        &self,
        area: Rect,
        buf: &mut Buffer,
        mask_char: Option<char>,
        textarea_right_reserve: u16,
    )

作用:这是这段里最核心的绘制函数,负责把输入框、页脚、弹窗、附件、占位文字和高亮全部画出来。它像排版师一样决定每一块该放在哪里、空间不够时该省掉什么。

数据流:进入屏幕区域、Buffer、可选遮罩字符和右侧预留宽度 → 先切分出 composer、远程图片、文本框、弹窗区域;如果有弹窗就画弹窗,否则按当前页脚状态选择历史搜索、计划提示、闪烁提示、状态行、快捷键提示和右侧上下文;然后画背景、附件、提示符、文本框内容、高亮或遮罩;最后在禁用输入或空输入时画灰色占位文字 → 输出是 Buffer 里完整的底部输入界面,同时会借用并更新 textarea 的渲染状态。

调用关系:render_with_mask 和 render 会把绘制工作交给它;它调用 layout_areas_with_textarea_right_reserve 做区域切分,调用 footer_props、custom_footer_height、mode_indicator_line、right_footer_line_with_context、shell_mode_footer_line、plugin_at_mention_highlights 等方法来收集要画的内容。

调用图:调用 25 个内部函数(custom_footer_height, footer_props, is_in_paste_burst, layout_areas_with_textarea_right_reserve, mode_indicator_line, plugin_at_mention_highlights, right_footer_line_with_context, shell_mode_footer_line, flash_visible, plan_mode_nudge_line (+15 more));被 2 处调用(render, render_with_mask);外部调用 15 个(default, set_span, Length, vertical, from, new, new, footer_spacing, from, render_ref (+5 more))。

tests::snapshot_composer_state_with_width4619–4649 ↗
fn snapshot_composer_state_with_width(
        name: &str,
        width: u16,
        enhanced_keys_supported: bool,
        setup: F,
    )

作用:这是测试用的截图辅助函数:用指定宽度画出 composer,然后和保存的快照对比。快照测试可以理解成“界面照片比对”。

数据流:进入快照名、宽度、是否支持增强按键和一个 setup 设置函数 → 创建 composer,让 setup 改造状态,计算需要高度,放进测试终端渲染 → 用 insta 保存或比较渲染结果。

调用关系:footer_mode_snapshots 和 footer_collapse_snapshots 大量调用它;它统一了创建 composer、渲染和 assert_snapshot 的流程。

调用图:调用 4 个内部函数(new, footer_spacing, new, footer_height);外部调用 3 个(new, assert_snapshot!, new)。

tests::snapshot_composer_state4651–4661 ↗
fn snapshot_composer_state(name: &str, enhanced_keys_supported: bool, setup: F)

作用:这是默认宽度为 100 的快照测试辅助函数。它让测试不用每次都写同样的宽度参数。

数据流:进入快照名、增强按键开关和 setup 函数 → 调用 snapshot_composer_state_with_width,宽度固定传 100 → 输出是一次快照断言。

调用关系:footer_mode_snapshots 用它来生成多种页脚模式的标准宽度快照;具体渲染仍由 snapshot_composer_state_with_width 完成。

调用图:外部调用 1 个(snapshot_composer_state_with_width)。

tests::shell_command_cursor_uses_absorbed_prefix4802–4821 ↗
fn shell_command_cursor_uses_absorbed_prefix()

作用:测试 Shell 命令模式下光标位置是否把开头的感叹号前缀算对。这里的“吸收前缀”意思是界面把 ! 当成模式提示的一部分处理。

数据流:创建 composer 和测试区域 → 分别设置“!git”和“! git”,把光标移到末尾 → 调用 cursor_pos 并断言坐标符合预期。

调用关系:它验证 cursor_pos 的结果;如果 Shell 模式改变了前缀显示方式,这个测试能发现光标偏移错误。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, new, assert_eq!)。

tests::shell_command_uses_shell_accent_style4824–4859 ↗
fn shell_command_uses_shell_accent_style()

作用:测试 Shell 命令模式使用红色强调样式。这样用户一眼能看出当前输入会被当作 shell 命令,而不是普通聊天。

数据流:创建 composer,打开状态行,设置文本为“!git status” → 渲染到测试 Buffer → 检查左侧提示符是红色 !,页脚里的 Shell mode 标签也是红色。

调用关系:它验证 render 对 Shell 模式提示符和页脚标签的样式处理,依赖 set_status_line_enabled、set_status_line 和 set_text_content 制造场景。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(empty, from, new, new, assert_eq!)。

tests::plugin_mention_foreground_color4861–4880 ↗
fn plugin_mention_foreground_color(composer: &ChatComposer) -> Option<Color>

作用:测试辅助函数,用来取出输入框里 @sample 插件提及的前景色。它把颜色检查这件事封装起来,方便多个测试复用。

数据流:进入一个 composer 引用 → 渲染到 40x5 的 Buffer,读出文本框那一行,找到“@sample”的起始位置 → 返回该单元格的前景色。

调用关系:plugin_at_mentions_use_plugin_accent_style 和 recalled_plugin_at_mentions_keep_plugin_accent_style 会调用它;它内部调用 composer.render。

调用图:调用 1 个内部函数(render);外部调用 2 个(empty, new)。

tests::plugin_at_mentions_use_plugin_accent_style4883–4908 ↗
fn plugin_at_mentions_use_plugin_accent_style()

作用:测试插件 @ 提及会用插件强调色显示。这里预期颜色是洋红色。

数据流:创建 composer,设置文本“@sample plugin”并附上一个插件 MentionBinding → 调用 plugin_mention_foreground_color → 断言颜色是 Magenta。

调用关系:它验证 plugin_at_mention_highlights 最终在 render 中生效;颜色读取交给测试辅助函数 plugin_mention_foreground_color。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::plugin_at_mentions_render_with_plugin_accent_snapshot4911–4959 ↗
fn plugin_at_mentions_render_with_plugin_accent_snapshot()

作用:用快照方式测试插件 @ 提及哪些字符被标成洋红色。不只看一个点,而是看整段高亮范围。

数据流:创建 composer 并设置带插件绑定的 @sample 文本 → 渲染后逐列读取文字和颜色,把洋红色位置转成 ^ 标记 → 用快照断言整行文本和高亮位置。

调用关系:它直接验证 render 产生的 Buffer;比单点颜色测试更能发现高亮范围变短或变长的问题。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(empty, new, new, new, assert_snapshot!, vec!)。

tests::recalled_plugin_at_mentions_keep_plugin_accent_style4962–4995 ↗
fn recalled_plugin_at_mentions_keep_plugin_accent_style()

作用:测试提交过的插件 @ 提及从历史里召回后,仍然保持插件颜色。这样历史消息不会丢掉提及绑定信息。

数据流:创建 composer,输入带插件绑定的文本并按 Enter 提交 → 清空当前文本,再按 Up 从历史召回 → 检查需要重画,并确认 @sample 仍是洋红色。

调用关系:它覆盖提交、历史召回和重新绑定提及的整条路;最终用 plugin_mention_foreground_color 检查渲染效果。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, new, new, assert!, assert_eq!, vec!)。

tests::esc_exits_empty_shell_mode5031–5057 ↗
fn esc_exits_empty_shell_mode()

作用:测试只输入一个 ! 进入空 Shell 模式后,按 Esc 会退出 Shell 模式并清空输入。

数据流:创建 composer,像真人一样输入 ! → 确认进入 bash/shell 模式且文本是 ! → 模拟 Esc → 断言没有提交结果、需要重画、Shell 模式关闭、文本变空。

调用关系:它验证 handle_key_event 对空 Shell 模式 Esc 的特殊处理;输入动作由 type_chars_humanlike 模拟。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, type_chars_humanlike)。

tests::esc_keeps_shell_mode_when_paste_burst_flushes_pending_text5060–5087 ↗
fn esc_keeps_shell_mode_when_paste_burst_flushes_pending_text()

作用:测试粘贴突发期间按 Esc 时,不会错误退出 Shell 模式,而是先把待处理字符刷进输入框。粘贴突发可以理解成终端一次塞进来一串字符时的缓冲状态。

数据流:创建 composer,输入 ! 后再输入 g,使 g 暂存在粘贴突发里 → 确认当前文本还只有 ! → 按 Esc → 断言需要重画、仍在 Shell 模式、文本变成 !g。

调用关系:它验证 handle_key_event 和 is_in_paste_burst 的配合;防止 Esc 在有待刷新的输入时做错动作。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, assert!, assert_eq!, type_chars_humanlike)。

tests::esc_hint_stays_hidden_with_draft_content5329–5355 ↗
fn esc_hint_stays_hidden_with_draft_content()

作用:测试输入框已经有草稿内容时,按 Esc 不会弹出 Esc 返回提示。否则用户正在编辑时会被无关提示打扰。

数据流:创建 composer,输入字符 d → 确认不是空输入且没有弹窗 → 按 Esc → 断言页脚仍是普通空 composer 模式,esc_backtrack_hint 仍为 false。

调用关系:它验证 handle_key_event 对有草稿内容时的 Esc 行为;防止 set_esc_backtrack_hint 之类的提示逻辑误触发。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, type_chars_humanlike)。

tests::empty_vim_insert_escape_enters_normal_without_esc_hint5358–5393 ↗
fn empty_vim_insert_escape_enters_normal_without_esc_hint()

作用:测试 Vim 模式下,空输入框处于插入模式时按 Esc 应该回到普通模式,而不是显示 Esc 提示。Vim 模式是模仿 Vim 编辑器的插入/普通两种状态。

数据流:创建 composer,启用 Vim,按 i 进入插入模式 → 确认输入为空且状态是 Vim: Insert → 按 Esc → 断言变成 Vim: Normal、输入仍为空、页脚不显示 Esc backtrack 提示。

调用关系:它验证 Vim 按键处理优先级;handle_key_event 应先满足 Vim 模式切换,而不是普通页脚 Esc 提示。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::slash_opens_command_popup_in_vim_normal_mode5396–5424 ↗
fn slash_opens_command_popup_in_vim_normal_mode()

作用:测试在 Vim 普通模式下按 / 会打开斜杠命令弹窗,并进入可输入状态。这样用户能像普通模式一样快速开始命令。

数据流:创建 composer,启用 Vim → 模拟按 / → 断言文本变成 /、光标在末尾、命令弹窗打开、Vim 指示变成 Insert。

调用关系:它验证 handle_key_event 对 Vim 普通模式里 / 的特殊入口;弹窗状态通过 ActivePopup::Command 检查。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::slash_command_can_be_typed_and_dispatched_after_vim_normal_slash5427–5459 ↗
fn slash_command_can_be_typed_and_dispatched_after_vim_normal_slash()

作用:测试从 Vim 普通模式按 / 打开命令后,可以继续输入完整命令并按 Enter 执行。这里例子是 /diff。

数据流:创建 composer,启用 Vim → 依次输入 /、d、i、f、f → 确认文本是 /diff 且命令弹窗还开着;再按 Enter → 断言需要重画、输入框清空、Vim 回到 Normal,并返回 Diff 命令结果。

调用关系:它串起 Vim 普通模式入口、命令弹窗输入和命令派发;最终结果通过 InputResult::Command(SlashCommand::Diff) 验证。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::inline_slash_command_dispatch_resets_vim_mode_to_normal5462–5498 ↗
fn inline_slash_command_dispatch_resets_vim_mode_to_normal()

作用:测试用户在 Vim 插入模式里输入内联斜杠命令并回车后,命令会被正确识别,而且 Vim 状态会回到普通模式。

数据流:先创建一个输入框,打开协作模式和 Vim 模式,再模拟按 i 进入插入模式,并放入“/plan investigate this”。按回车后,它检查输出是不是 Plan 命令、参数是不是“investigate this”,同时检查界面需要重画,Vim 指示器变成“Normal”。

调用关系:这个测试走的是 ChatComposer::handle_key_event 这条真实按键入口,验证斜杠命令提交完成后,输入框会顺手把 Vim 模式复位,避免下一次输入还留在错误状态。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, panic!)。

tests::bang_enters_shell_mode_in_vim_normal_mode5501–5529 ↗
fn bang_enters_shell_mode_in_vim_normal_mode()

作用:测试在 Vim 普通模式下按感叹号 ! 会进入 shell 命令模式。shell 命令模式就是把后面的内容当作终端命令来写。

数据流:先创建输入框并打开 Vim 模式,然后模拟按下 !。结果不提交任何内容,但要求界面重画;草稿被标记为 bash/shell 模式,当前显示文本是“!”,真正可编辑区域里是空字符串,Vim 状态切到插入模式。

调用关系:它验证普通模式里的特殊快捷键分支:! 不是普通文字输入,而是触发 shell 模式,并把后续输入交给文本编辑区域继续接收。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::shell_command_can_be_typed_after_vim_normal_bang5532–5556 ↗
fn shell_command_can_be_typed_after_vim_normal_bang()

作用:测试按 ! 进入 shell 模式后,用户可以继续正常输入命令内容。

数据流:创建输入框,打开 Vim 模式,依次模拟输入 !、e、c、h、o。最后检查草稿仍是 shell 模式,用户看到的是“!echo”,实际命令正文是“echo”,并且没有打开任何弹窗。

调用关系:它接在上一类行为之后,确认 ! 只是开启模式,不会挡住后面的字符输入,也不会误触发其他提示弹窗。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::clear_for_ctrl_c_records_cleared_draft5585–5604 ↗
fn clear_for_ctrl_c_records_cleared_draft()

作用:测试用户按 Ctrl+C 清空草稿时,被清掉的文字会进入历史记录,之后还能找回来。

数据流:先把输入框内容设为“draft text”,调用 clear_for_ctrl_c。函数返回被清掉的文字,输入框变空;再向上翻历史,能拿到同样内容的 HistoryEntry。

调用关系:它验证 Ctrl+C 清空不是直接丢弃内容,而是把草稿交给历史记录系统保存,方便用户误清后恢复。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::clear_for_ctrl_c_preserves_pending_paste_history_entry5607–5676 ↗
fn clear_for_ctrl_c_preserves_pending_paste_history_entry()

作用:测试很大的粘贴内容被 Ctrl+C 清掉后,历史记录里不只保存占位文字,还保存真正的大段内容。

数据流:先粘贴一段超过阈值的大文本,输入框显示类似“[Pasted Content N chars]”的占位符,同时内部保存原文。Ctrl+C 清空后,历史记录包含占位符、文本元素和待提交的原始粘贴内容。恢复历史后再按回车,最终提交出去的是完整原文,不是占位符。

调用关系:它覆盖大粘贴、历史恢复、提交这几段流程的串联,防止用户看到的占位符和真正要发给模型的内容脱节。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, assert!, assert_eq!, format!, panic!, vec!)。

tests::large_paste_numbering_reuses_after_ctrl_c_clear5679–5705 ↗
fn large_paste_numbering_reuses_after_ctrl_c_clear()

作用:测试大粘贴被 Ctrl+C 清掉后,再粘贴同样的大内容,占位符编号或名字可以重新使用,不会越滚越乱。

数据流:先粘贴一段大文本,得到一个固定占位符。调用 clear_for_ctrl_c 后,文本区和待粘贴列表都清空。再粘贴同样内容,检查占位符还是原来的名字,待粘贴列表也只有一项。

调用关系:它验证清空草稿时,大粘贴相关的内部计数和临时状态也会被清掉,下一次输入从干净状态开始。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, assert_eq!, format!)。

tests::vim_mode_resets_to_normal_after_submission5708–5746 ↗
fn vim_mode_resets_to_normal_after_submission()

作用:测试 Vim 模式下成功提交普通消息后,输入框会清空,并回到 Vim 普通模式。

数据流:创建输入框,打开 steer 功能和 Vim 模式,按 i 进入插入模式,填入“h”,再按回车。结果是提交文本“h”,输入框清空,Vim 仍启用但状态显示为 Normal。

调用关系:它验证正常提交路径会调用清理和 Vim 复位逻辑,让下一轮输入从可预期的普通模式开始。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, panic!)。

tests::vim_mode_resets_to_normal_after_queued_submission5749–5780 ↗
fn vim_mode_resets_to_normal_after_queued_submission()

作用:测试任务正在运行时,如果输入被排队提交,Vim 状态也会回到普通模式。

数据流:先让输入框处于任务运行中,再打开 Vim 模式,进入插入模式并放入“queued”。调用 handle_submission 并允许排队后,结果是 Queued,文本内容是“queued”,输入框清空,Vim 指示器为 Normal。

调用关系:它覆盖“不能马上发、先排队”的提交路径,确保这条路和普通提交一样会收尾清理 Vim 状态。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, new, assert!, assert_eq!, panic!)。

tests::vim_mode_stays_insert_after_suppressed_submission5783–5811 ↗
fn vim_mode_stays_insert_after_suppressed_submission()

作用:测试当输入看起来像斜杠命令但不是有效命令时,回车不会提交,也不会强行把 Vim 模式改回普通模式。

数据流:输入框进入 Vim 插入模式,文本设为“/not-a-command”,按回车。结果是不提交,文本保留,Vim 状态仍是 Insert。

调用关系:它验证被拦下的提交不会触发正常提交后的清理逻辑,用户可以继续编辑那段无效命令。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, new, assert!, assert_eq!)。

tests::esc_switches_vim_insert_to_normal5814–5848 ↗
fn esc_switches_vim_insert_to_normal()

作用:测试在 Vim 插入模式中按 Esc 会切回普通模式,并像 Vim 那样把光标往左挪一格。

数据流:先进入插入模式,文本设为“hey”,光标放到末尾。按 Esc 后,Vim 指示器变成 Normal,光标从末尾退到 y 前面的位置。

调用关系:它验证按键处理里 Esc 对 Vim 编辑状态的特殊处理,保证行为符合熟悉 Vim 的用户预期。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, new, assert_eq!)。

tests::vim_insert_uses_bar_cursor_style5851–5888 ↗
fn vim_insert_uses_bar_cursor_style()

作用:测试 Vim 插入模式下光标显示成竖线,普通模式或非 Vim 状态下使用默认光标样式。

数据流:先记录默认光标样式,再打开 Vim 模式,此时仍应默认。按 i 进入插入模式并输入文字后,cursor_style 返回竖线样式;按 Esc 回普通模式后,又回到默认样式。

调用关系:它验证渲染层会根据编辑模式改变光标外观,让用户不用看文字也能感知当前 Vim 状态。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, new, new, assert_eq!)。

tests::clear_for_ctrl_c_preserves_image_draft_state5891–5934 ↗
fn clear_for_ctrl_c_preserves_image_draft_state()

作用:测试带本地图片的草稿被 Ctrl+C 清掉后,历史记录仍能保存图片占位符和图片路径。

数据流:先附加 example.png,输入框出现图片占位符。Ctrl+C 清空后,从历史取回条目,里面包含占位符、文本元素和本地图片路径。恢复历史后,输入框文本、图片路径和占位符载荷都还在。

调用关系:它验证图片附件不是普通文字,清空和恢复时必须连同路径一起保存,否则用户恢复草稿时图片会丢。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 4 个(from, assert!, assert_eq!, vec!)。

tests::clear_for_ctrl_c_preserves_remote_offset_image_labels5937–5976 ↗
fn clear_for_ctrl_c_preserves_remote_offset_image_labels()

作用:测试同时存在远程图片和本地图片时,Ctrl+C 保存历史不会把图片编号和占位符对应关系搞错。

数据流:先设置一个远程图片 URL,再放入含“[Image #2] draft”的文本和一个本地图片路径。清空后,历史记录应保存原文字、文本元素、本地图片、远程图片 URL,并保持“[Image #2]”这个占位符。

调用关系:它覆盖远程图片已经占了编号、本地图片从后续编号开始的情况,防止历史恢复时图片标签错位。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(from, assert_eq!, vec!)。

tests::apply_history_entry_preserves_local_placeholders_after_remote_prefix5979–6013 ↗
fn apply_history_entry_preserves_local_placeholders_after_remote_prefix()

作用:测试从历史恢复时,如果前面已有远程图片,本地图片的占位符编号仍要保持原样。

数据流:构造一个历史条目,文本是“[Image #2] draft”,包含一个本地图片和一个远程图片。应用到输入框后,当前文本、文本元素、本地路径、远程 URL 都和历史条目一致。

调用关系:它验证 apply_history_entry 这条恢复路径会尊重历史里的占位符,不会因为重新计算图片列表而改掉用户看到的标签。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(from, new, assert_eq!, with_pending_and_remote, vec!)。

tests::question_mark_only_toggles_on_first_char6018–6055 ↗
fn question_mark_only_toggles_on_first_char()

作用:测试问号 ? 只有在输入框为空、作为第一个字符时才打开快捷键说明;已有草稿时它应该只是普通文字。

数据流:空输入框按 ?,结果打开 ShortcutOverlay 并要求重画;再按一次关闭。输入 h 后再按 ?,它进入文本,最终文本是“h?”,底部实际模式仍按“有草稿”计算。

调用关系:它验证 ? 的双重身份:空框时是帮助入口,有内容时是用户要输入的字符。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(Char, new, assert!, assert_eq!, flush_after_paste_burst, type_chars_humanlike)。

tests::shift_question_mark_toggles_shortcut_overlay_when_empty6058–6079 ↗
fn shift_question_mark_toggles_shortcut_overlay_when_empty()

作用:测试 Shift+? 在输入框为空时也能打开快捷键说明。

数据流:创建输入框并开启 steer 功能,然后模拟带 Shift 的 ? 按键。结果不产生提交,但需要重画,并把底部模式切到 ShortcutOverlay。

调用关系:它补充验证不同键盘事件写法下的问号快捷键,确保用户按常见的 Shift+/ 也能看到帮助。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::question_mark_does_not_toggle_during_paste_burst6084–6115 ↗
fn question_mark_does_not_toggle_during_paste_burst()

作用:测试正在识别一段快速粘贴时,里面的 ? 不会误打开快捷键说明。

数据流:先强制进入 paste burst。随后快速输入 hi?there,这些字符暂存在粘贴缓冲里,文本区暂时为空。刷新粘贴后,文本变成“hi?there”,底部没有进入 ShortcutOverlay。

调用关系:它保护粘贴场景:粘贴内容里可能有 ?,系统不能把它当成用户手动按的帮助快捷键。

调用图:调用 2 个内部函数(new, new);外部调用 8 个(now, Char, new, new, assert!, assert_eq!, assert_ne!, flush_after_paste_burst)。

tests::set_connector_mentions_refreshes_open_mention_popup6118–6157 ↗
fn set_connector_mentions_refreshes_open_mention_popup()

作用:测试连接器数据更新后,如果用户正在输入 $,提及弹窗会立刻显示可用连接器。

数据流:先开启连接器功能,文本设为“$”,此时还没有弹窗。随后传入一个可用的 Notion 连接器,输入框打开 Skill 类型弹窗,选中项的插入文本是“$notion”,路径是“app://connector_1”。

调用关系:它验证外部连接器列表到达时,会触发提及候选列表刷新,让用户不用重新输入 $。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, panic!, vec!)。

tests::set_connector_mentions_skips_disabled_connectors6160–6195 ↗
fn set_connector_mentions_skips_disabled_connectors()

作用:测试被禁用的连接器不会出现在 $ 提及弹窗里。

数据流:开启连接器功能并输入“$”,然后传入一个 is_enabled 为 false 的 Notion 连接器。结果弹窗仍然没有打开。

调用关系:它验证 set_connector_mentions 会过滤不可用项,避免用户选到已经禁用、无法使用的应用。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, vec!)。

tests::set_plugin_mentions_refreshes_open_mention_popup6198–6228 ↗
fn set_plugin_mentions_refreshes_open_mention_popup()

作用:测试插件列表更新后,正在输入 $ 的用户会马上看到插件提及候选。

数据流:输入框文本设为“$”,再传入一个 Sample Plugin。结果打开提及弹窗,选中项插入“$sample”,路径指向“plugin://sample@test”。

调用关系:它验证插件能力数据和输入框弹窗的联动,插件到达后会被转换成可插入的提及项。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, panic!, vec!)。

tests::set_skill_mentions_refreshes_open_mention_popup6231–6265 ↗
fn set_skill_mentions_refreshes_open_mention_popup()

作用:测试技能列表更新后,正在输入 $ 时会打开技能提及弹窗。

数据流:输入“$”,再传入一个名为 codex 的 SkillMetadata。结果弹窗打开,选中项插入“$codex”,路径是该技能 SKILL.md 文件路径。

调用关系:它验证本地技能元数据会进入统一的提及候选列表,并能保留实际文件位置。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, assert!, assert_eq!, test_path_buf, panic!, vec!)。

tests::mention_items_show_plugin_owned_skill_and_app_duplicates6268–6340 ↗
fn mention_items_show_plugin_owned_skill_and_app_duplicates()

作用:测试同一个名字同时来自技能、插件和应用时,候选列表会全部显示,并用分类标签区分。

数据流:输入“$goog”,设置一个 Google Calendar 技能、一个同名插件和一个同名应用连接器。调用 mention_items 后得到 3 项,依次标记为 [Skill]、[Plugin]、[App],并各自带着不同路径。

调用关系:它验证提及列表不会因为名字相同就错误去重,而是保留来源差异,让用户能选对真正想用的对象。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_eq!, test_path_buf, vec!)。

tests::plugin_mention_popup_snapshot6343–6362 ↗
fn plugin_mention_popup_snapshot()

作用:用快照测试记录插件提及弹窗的长相。快照测试就是把界面渲染结果存下来,以后改代码时对比有没有变。

数据流:通过 snapshot_composer_state 创建一个固定场景:文本是“$sa”,并设置一个 Sample Plugin。测试框架会渲染输入框状态并和名为 plugin_mention_popup 的快照比较。

调用关系:它主要服务于界面回归测试,确保插件提及弹窗的显示内容和排版不会被无意改坏。

调用图:外部调用 1 个(snapshot_composer_state)。

tests::default_unified_mention_popup_snapshot6365–6384 ↗
fn default_unified_mention_popup_snapshot()

作用:用快照测试记录默认统一提及弹窗的显示效果,尤其是 @ 触发的新版本提及功能。

数据流:创建快照场景,按默认功能开关决定是否启用 MentionsV2,文本设为“@sa”,并提供一个插件候选。随后渲染并和 default_unified_mention_popup 快照对比。

调用关系:它验证功能开关、@ 输入和插件候选一起工作时,统一提及弹窗的界面保持稳定。

调用图:外部调用 1 个(snapshot_composer_state)。

tests::mention_popup_type_prefixes_snapshot6387–6443 ↗
fn mention_popup_type_prefixes_snapshot()

作用:用快照测试检查提及弹窗里不同类型候选的前缀和分类显示,例如技能、插件、应用。

数据流:在固定宽度 72 的界面里输入“$goog”,开启连接器,并设置 Google Calendar 的技能、插件和应用候选。渲染结果会和 mention_popup_type_prefixes 快照比较。

调用关系:它关注视觉层:当多个来源同名时,弹窗是否用清楚的类型前缀帮助用户区分。

调用图:外部调用 1 个(snapshot_composer_state_with_width)。

tests::set_connector_mentions_excludes_disabled_apps_from_mention_popup6446–6477 ↗
fn set_connector_mentions_excludes_disabled_apps_from_mention_popup()

作用:再次测试禁用的应用连接器不会进入提及弹窗,强调应用项也必须被过滤。

数据流:开启连接器功能,输入“$”,传入一个 is_enabled 为 false 的 Notion 应用。最后确认没有任何活动弹窗。

调用关系:它和跳过禁用连接器的测试相呼应,防止未来改 set_connector_mentions 时漏掉应用可用性判断。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, vec!)。

tests::shortcut_overlay_persists_while_task_running6480–6502 ↗
fn shortcut_overlay_persists_while_task_running()

作用:测试快捷键说明打开后,即使后台任务开始运行,也不会被底部状态栏自动挤掉。

数据流:先按 ? 打开 ShortcutOverlay,然后把任务状态设为 running。最后检查 footer.mode 和 footer_mode 都仍是 ShortcutOverlay。

调用关系:它验证底部状态栏的优先级:用户主动打开的帮助说明,比“任务正在运行”这类状态更应该保持可见。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(Char, new, assert_eq!)。

tests::test_current_at_token_basic_cases6505–6560 ↗
fn test_current_at_token_basic_cases()

作用:测试 current_at_token 能从光标附近识别基本的 @ 查询词,用来触发文件或提及补全。

数据流:逐个创建 TextArea,放入不同文本和光标位置,包括英文、数字、中文、土耳其字母、表情等。调用 ChatComposer::current_at_token 后,检查它返回预期的查询词,或在没有合法 @ 时返回 None。

调用关系:它直接测试 @ 识别的小工具函数,这是文件补全和提及弹窗能否正确打开的基础。

调用图:调用 2 个内部函数(current_at_token, new);外部调用 2 个(assert_eq!, vec!)。

tests::test_current_at_token_cursor_positions6563–6594 ↗
fn test_current_at_token_cursor_positions()

作用:测试光标在 @ 词的不同位置时,都能识别到同一个 @ 查询词。

数据流:准备若干文本,比如“@test”和“@file1 @file2”,把光标放在 @ 上、词中、词尾或第二个 token 上。调用 current_at_token,检查返回对应 token。

调用关系:它验证补全逻辑不只在光标位于词尾时有效,用户在词中移动光标也能得到正确候选。

调用图:调用 2 个内部函数(current_at_token, new);外部调用 2 个(assert_eq!, vec!)。

tests::test_current_at_token_whitespace_boundaries6597–6651 ↗
fn test_current_at_token_whitespace_boundaries()

作用:测试 @ 查询词必须有合适边界,比如前面是空格、全角空格或制表符;夹在普通单词中间则不算。

数据流:构造带普通空格、全角空格、tab,以及“aaa@aaa”这类中间 @ 的文本。调用 current_at_token 后,合法边界返回查询词,不合法的返回 None。

调用关系:它防止把邮箱、包名或普通单词里的 @ 错当成补全触发符,同时支持不同语言和输入习惯里的空白字符。

调用图:调用 2 个内部函数(current_at_token, new);外部调用 2 个(assert_eq!, vec!)。

tests::test_current_at_token_tracks_tokens_with_second_at6654–6679 ↗
fn test_current_at_token_tracks_tokens_with_second_at()

作用:测试一个 @ 查询词里还包含第二个 @ 时,识别逻辑不会从第二个 @ 截断。典型例子是 npm 包名加版本号。

数据流:文本是“npx -y @kaeawc/auto-mobile@latest”,把光标放在开头 @、包名中、版本 @ 和末尾。每次 current_at_token 都应返回完整的“kaeawc/auto-mobile@latest”。

调用关系:它保护命令行场景,避免文件补全逻辑误解带作用域和版本号的软件包名字。

调用图:调用 2 个内部函数(current_at_token, new);外部调用 2 个(assert_eq!, vec!)。

tests::test_current_at_token_allows_file_queries_with_second_at6682–6705 ↗
fn test_current_at_token_allows_file_queries_with_second_at()

作用:测试文件查询里出现第二个 @ 也允许继续识别,比如带 @2x 的图片文件名。

数据流:文本是“@icons/icon@2x.png”,光标放在前导 @、第二个 @ 前、第二个 @ 上和末尾。每次调用 current_at_token,都要求至少能识别出一个 token。

调用关系:它补充文件名场景,保证像高分辨率图片名这种常见写法不会破坏 @ 文件查询。

调用图:调用 2 个内部函数(current_at_token, new);外部调用 2 个(assert!, vec!)。

tests::test_current_at_token_ignores_mid_word_at6708–6727 ↗
fn test_current_at_token_ignores_mid_word_at()

作用:测试单词中间的 @ 不会触发 @ 补全。

数据流:文本是“foo@bar”,把光标放在 @ 处和末尾。调用 current_at_token 都应返回 None。

调用关系:它和边界测试一起防止误弹窗,比如用户输入邮箱或普通字符串时不应突然出现文件/提及选择。

调用图:调用 2 个内部函数(current_at_token, new);外部调用 2 个(assert_eq!, vec!)。

tests::set_text_content_rebinds_at_sigiled_mentions6730–6754 ↗
fn set_text_content_rebinds_at_sigiled_mentions()

作用:测试用已有绑定恢复文本时,@ 形式的提及会重新绑定到它的目标路径。

数据流:创建一个 MentionBinding,表示 @figma 对应某个 SKILL.md 路径。调用 set_text_content_with_mention_bindings 放入“@figma please”后,再读取 mention_bindings,应该和传入的一样。

调用关系:它验证恢复草稿或历史时,提及不只是普通文字,还会重新变成带目标地址的特殊文本片段。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::set_text_content_rebinds_matching_sigil_only6757–6787 ↗
fn set_text_content_rebinds_matching_sigil_only()

作用:测试绑定提及时只匹配相同符号:$figma 的绑定不应该套到 @figma 上。

数据流:传入一个只针对 $figma 的绑定,文本里同时有“@figma then $figma”。恢复后检查当前被绑定的 token 只有 $figma,并且保存的绑定列表没变。

调用关系:它保证 @ 和 $ 这两套提及入口互不串线,避免同名对象被绑定到错误来源。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::set_text_content_rebinds_both_sigil_forms6790–6830 ↗
fn set_text_content_rebinds_both_sigil_forms()

作用:测试同名提及同时有 @ 和 $ 两种形式时,两者都能各自正确绑定。

数据流:传入两个 MentionBinding:@figma 指向插件,$figma 指向应用。文本里也包含这两个 token。恢复后,当前提及元素应同时包含 @figma 和 $figma,绑定列表保持原样。

调用关系:它验证多来源同名提及可以共存,并靠符号区分,不会互相覆盖。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::set_text_content_rebinds_at_mentions_after_email_substrings6833–6871 ↗
fn set_text_content_rebinds_at_mentions_after_email_substrings()

作用:测试文本里有邮箱样式的 foo@sample.com 时,恢复 @sample 提及不会把邮箱里的 @sample 错绑成特殊元素。

数据流:文本是“foo@sample.com then @sample”,绑定只针对真正独立的 @sample。恢复后检查 textarea 里的特殊文本元素范围只覆盖后面的 @sample,邮箱部分仍是普通可编辑文字。

调用关系:它保护邮件地址和普通文本编辑体验,避免提及重绑定太贪心,把不该锁住的文字锁住。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::set_text_content_rebinds_at_mentions_after_punctuation6874–6911 ↗
fn set_text_content_rebinds_at_mentions_after_punctuation()

作用:测试 @ 提及出现在标点后面时也能正确重新绑定,比如括号里的 @sample。

数据流:文本是“Please ask (@sample)”,传入 @sample 的绑定。恢复后检查特殊文本元素的范围正好覆盖 @sample,并保存绑定。

调用关系:它补足边界规则:提及不一定只出现在空格后,也可能跟在括号等标点后面。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::bound_at_mentions_do_not_block_arrow_navigation6914–6956 ↗
fn bound_at_mentions_do_not_block_arrow_navigation()

作用:测试已经绑定的 @ 提及不会妨碍左右方向键移动光标,也不会因此打开提及弹窗。

数据流:把文本设为“go @figma now”,并绑定 @figma。光标从“go”后开始,连续按右键会跳过空格和绑定提及,再按左键会往回移动。每一步都检查光标位置正确,弹窗始终关闭。

调用关系:它验证特殊提及元素虽然是一个有意义的整体,但键盘导航仍要自然,不应把用户困在元素边界上。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, new, assert!, assert_eq!, vec!)。

tests::restored_bound_at_mentions_do_not_open_mention_popup6959–7012 ↗
fn restored_bound_at_mentions_do_not_open_mention_popup()

作用:测试从历史或外部状态恢复出来的已绑定 @ 提及,不会因为光标在附近就自动打开提及弹窗,并且可以直接提交。

数据流:对两个文本场景分别测试:纯“@sample”和句子里的“Please ask @sample.”。先设置插件候选,再用绑定恢复文本;确认没有弹窗。按回车后,结果应是正常 Submitted,提交文本保持原样。

调用关系:它验证恢复后的绑定提及已经是确定目标,不需要再次弹选择框,也不应该阻止用户发送消息。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, new, assert!, assert_eq!, panic!, vec!)。

tests::enter_submits_when_file_popup_has_no_selection7015–7044 ↗
fn enter_submits_when_file_popup_has_no_selection()

作用:测试 @ 文件弹窗打开但没有可选项时,按回车应该提交整条消息,而不是卡在弹窗里。

数据流:把文本设为“npx -y @kaeawc/auto-mobile@latest”,同步弹窗后确认打开的是 File 弹窗。按回车后,按键被消费,结果是 Submitted,提交文本就是原输入。

调用关系:它连接 current_at_token 的复杂 @ 包名场景和提交逻辑,确保误开的空文件弹窗不会拦住用户正常发送命令。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::enter_submits_when_unified_mention_popup_has_no_selection7047–7077 ↗
fn enter_submits_when_unified_mention_popup_has_no_selection()

作用:验证新版提及弹窗打开但没有选中任何项时,按回车应该正常提交整段输入,而不是误选弹窗内容。

数据流:测试先创建一个输入框,打开新版提及功能,填入包含 @ 的命令文本并同步弹窗 → 确认当前确实显示提及弹窗 → 模拟按回车 → 得到 Submitted 结果,提交文本保持原样。

调用关系:这是测试运行器执行的用例。它主要把场景交给 ChatComposer::handle_key_event,检查弹窗逻辑不会抢走回车提交。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::ascii_prefix_survives_non_ascii_followup7083–7109 ↗
fn ascii_prefix_survives_non_ascii_followup()

作用:验证先输入英文数字、再输入非 ASCII 字符时,前面的字符不会因为粘贴突发检测而丢掉。

数据流:测试创建输入框 → 输入字符 1,此时进入短暂的“疑似粘贴”状态 → 再输入日文字符 あ → 按回车 → 最终提交文本是 1あ。

调用关系:它覆盖 ChatComposer 的按键入口,专门防止粘贴检测和 Unicode 字符处理互相干扰。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, assert!, assert_eq!, panic!)。

tests::non_ascii_char_inserts_immediately_without_burst_state7114–7133 ↗
fn non_ascii_char_inserts_immediately_without_burst_state()

作用:验证单个非英文字符会立刻进入输入框,不会被误认为快速粘贴的一部分。

数据流:测试创建输入框 → 模拟输入 あ → 读取输入框文字和粘贴突发状态 → 文字已经是 あ,并且没有进入粘贴突发状态。

调用关系:它直接调用 handle_key_event,保证普通用户输入中文、日文这类字符时体验像正常打字。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::non_ascii_burst_buffers_enter_and_flushes_multiline7138–7167 ↗
fn non_ascii_burst_buffers_enter_and_flushes_multiline()

作用:验证在明确的快速粘贴状态里,中文字符和回车会先缓存,最后一起变成多行文本。

数据流:测试手动启动粘贴突发缓存 → 依次输入 你、好、回车、h、i → 输入框暂时仍为空 → 等待并刷新缓存 → 输入框变成 你好 换行 hi。

调用关系:它使用辅助函数 tests::flush_after_paste_burst,检查 ChatComposer 的粘贴缓存最终能正确落到文本框。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(now, Char, new, new, assert!, assert_eq!, flush_after_paste_burst)。

tests::non_ascii_burst_preserves_ideographic_space_and_ascii7172–7203 ↗
fn non_ascii_burst_preserves_ideographic_space_and_ascii()

作用:验证快速粘贴里包含中文、全角空格和英文时,字符形态都能原样保留。

数据流:测试启动粘贴突发 → 输入 你、全角空格、好、回车、h、i → 刷新前输入框为空 → 刷新后得到 你 好 换行 hi。

调用关系:它和粘贴缓存刷新流程配合,防止全角空格这类容易被忽略的字符被改成普通空格或丢失。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(now, Char, new, new, assert!, assert_eq!, flush_after_paste_burst)。

tests::non_ascii_burst_buffers_large_multiline_mixed_ascii_and_unicode7209–7254 ↗
fn non_ascii_burst_buffers_large_multiline_mixed_ascii_and_unicode()

作用:验证一大段混合中文、英文、UTF-8 字符和多行换行的粘贴内容能完整保存。

数据流:测试准备一段较大的多行混合文本 → 手动进入粘贴突发 → 把每个字符或换行逐个送入输入框 → 刷新前不显示 → 刷新后输入框内容与原文完全一致。

调用关系:它压力测试 handle_key_event 和粘贴突发缓存,确保大段 Unicode 文本不会被拆坏。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(now, Char, new, new, assert!, assert_eq!, flush_after_paste_burst)。

tests::ascii_burst_treats_enter_as_newline7259–7307 ↗
fn ascii_burst_treats_enter_as_newline()

作用:验证快速输入英文时,期间按回车应当被当成换行,而不是提交消息。

数据流:测试用带时间的输入接口快速输入 hi → 在粘贴突发期间触发提交逻辑 → 结果是 None,不提交 → 继续输入 there → 到期刷新缓存 → 文本框得到 hi 换行 there。

调用关系:它直接测试 handle_input_basic_with_time、handle_submission_with_time 和 handle_paste_burst_flush 的配合。

调用图:调用 3 个内部函数(new, new, recommended_active_flush_delay);外部调用 6 个(from_millis, now, Char, new, assert!, assert_eq!)。

tests::queued_submission_flushes_ascii_burst_instead_of_inserting_newline7312–7351 ↗
fn queued_submission_flushes_ascii_burst_instead_of_inserting_newline()

作用:验证如果当前要把输入排队,快速输入缓存会先被提交成队列内容,而不是把回车变成换行。

数据流:测试快速输入 hi 并确认处于粘贴突发 → 调用提交逻辑且 should_queue 为真 → 返回 Queued,文本是 hi → 输入框清空,粘贴突发结束。

调用关系:它检查排队提交优先于“回车变换行”的规则,避免任务忙时用户输入被改形。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(from_millis, now, Char, new, assert!, assert_eq!)。

tests::slash_context_enter_ignores_paste_burst_enter_suppression7356–7382 ↗
fn slash_context_enter_ignores_paste_burst_enter_suppression()

作用:验证输入框里是斜杠命令时,即使处于粘贴突发状态,回车也应该执行命令。

数据流:测试把输入框设成 /diff 并把光标放到末尾 → 手动启动粘贴突发 → 按回车 → 得到 Diff 命令结果。

调用关系:它让斜杠命令解析走在粘贴回车抑制规则前面,防止命令被错误当成普通多行文本。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(now, new, new, assert!)。

tests::non_char_key_flushes_active_burst_before_input7387–7418 ↗
fn non_char_key_flushes_active_burst_before_input()

作用:验证在快速粘贴缓存还没落地时,按方向键这类非文字键会先把缓存写进输入框。

数据流:测试启动粘贴突发 → 输入 h 和 i,文本框暂时为空 → 按左方向键 → 缓存先变成 hi,再移动光标到位置 1,粘贴突发结束。

调用关系:它覆盖 handle_key_event 对非字符键的处理,保证光标操作不会发生在空文本上。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(now, Char, new, new, assert!, assert_eq!)。

tests::disable_paste_burst_flushes_pending_first_char_and_inserts_immediately7423–7451 ↗
fn disable_paste_burst_flushes_pending_first_char_and_inserts_immediately()

作用:验证关闭粘贴突发功能时,已经暂存的第一个字符不会丢,后续字符也会立刻插入。

数据流:测试输入 a,让它进入粘贴突发缓存 → 中途把禁用开关打开 → a 被写入文本框并退出缓存 → 再输入 b → 文本框变成 ab。

调用关系:它测试配置切换和输入缓存之间的关系,防止用户改设置时丢字。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::handle_paste_small_inserts_text7456–7482 ↗
fn handle_paste_small_inserts_text()

作用:验证小段粘贴会直接放进输入框,并且按回车能正常提交。

数据流:测试粘贴 hello → 输入框立刻显示 hello,没有待替换的大粘贴记录 → 按回车 → 提交文本是 hello。

调用关系:它检查 handle_paste 和 handle_key_event 的基本路径,是粘贴功能最普通的用法。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::empty_enter_returns_none7485–7509 ↗
fn empty_enter_returns_none()

作用:验证空输入框按回车不会提交空消息。

数据流:测试创建空输入框 → 确认没有文字 → 按回车 → 返回 InputResult::None。

调用关系:它保护提交入口,避免用户误按回车产生空请求。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, panic!)。

tests::handle_paste_large_uses_placeholder_and_replaces_on_submit7514–7545 ↗
fn handle_paste_large_uses_placeholder_and_replaces_on_submit()

作用:验证大段粘贴不会直接把整个文本塞满界面,而是先显示占位符,提交时再还原原文。

数据流:测试生成超过阈值的大文本 → 粘贴后输入框显示 [Pasted Content ... chars] → pending_pastes 里保存占位符和原文 → 按回车 → 提交文本变回完整大文本,待粘贴记录被清空。

调用关系:它测试大粘贴的显示和提交闭环,保证界面清爽但实际内容不丢。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, format!, panic!)。

tests::submit_at_character_limit_succeeds7548–7573 ↗
fn submit_at_character_limit_succeeds()

作用:验证刚好等于最大字符数限制的输入可以提交。

数据流:测试开启 steer 功能 → 填入长度正好等于 MAX_USER_INPUT_TEXT_CHARS 的文本 → 按回车 → 返回 Submitted,文本不变。

调用关系:它检查边界值,防止限制条件写成“少于”而误拒绝合法输入。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(new, assert!)。

tests::oversized_submit_reports_error_and_restores_draft7576–7615 ↗
fn oversized_submit_reports_error_and_restores_draft()

作用:验证超过最大字符数的直接提交会失败、显示错误,并把草稿留在输入框里。

数据流:测试填入超出限制 1 个字符的文本 → 按回车 → 返回 None,输入框仍保留原文 → 从事件通道里找到一条错误历史消息。

调用关系:它通过 AppEventSender 检查错误提示是否发给上层界面,保证用户知道为什么没提交。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::oversized_queued_submission_reports_error_and_restores_draft7618–7657 ↗
fn oversized_queued_submission_reports_error_and_restores_draft()

作用:验证超过最大字符数的排队提交也会被拒绝,并且不会清掉用户草稿。

数据流:测试关闭 steer,让输入更像要排队 → 填入超长文本 → 按回车 → 结果是 None,文本仍在 → 事件通道里能读到超长输入错误。

调用关系:它覆盖排队路径的同一条长度保护规则,避免绕过正常提交检查。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::edit_clears_pending_paste7662–7684 ↗
fn edit_clears_pending_paste()

作用:验证大粘贴的占位符被用户编辑掉后,后台保存的原始大粘贴也会清理。

数据流:测试粘贴一段大文本 → pending_pastes 里有一条记录 → 按退格改动占位符 → pending_pastes 被清空。

调用关系:它防止占位符和真实内容不同步,避免提交时把用户已经删除的旧大文本又带出去。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::ui_snapshots7687–7747 ↗
fn ui_snapshots()

作用:用快照测试检查输入框在空、小粘贴、大粘贴、多次粘贴和删除后的终端画面是否稳定。

数据流:测试为每个场景创建新的 ChatComposer → 按场景粘贴或退格 → 用测试终端渲染 → 把画面和保存的快照比对。

调用关系:它调用 composer.render,把逻辑状态交给界面绘制层,防止视觉布局被无意改坏。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, new, assert_snapshot!, panic!, new, vec!)。

tests::image_placeholder_snapshots7750–7767 ↗
fn image_placeholder_snapshots()

作用:检查本地图片附件在输入框里显示的占位行是否符合预期。

数据流:测试通过快照辅助函数分别附加一张和多张图片 → 渲染 composer 状态 → 和快照比较。

调用关系:它把场景交给 snapshot_composer_state,重点验证 attach_image 后的显示效果。

调用图:外部调用 1 个(snapshot_composer_state)。

tests::remote_image_rows_snapshots7770–7817 ↗
fn remote_image_rows_snapshots()

作用:检查远程图片链接在输入框里的显示、选中和删除后的画面是否正确。

数据流:测试设置两个远程图片 URL 和文字 → 分别渲染普通状态、按上键选中状态、再删除第一项后的状态 → 保存或比对快照。

调用关系:它通过 handle_key_event 模拟键盘选择和删除,验证图片行和文本输入区能一起工作。

调用图:外部调用 1 个(snapshot_composer_state)。

tests::slash_popup_model_first_for_mo_ui7820–7848 ↗
fn slash_popup_model_first_for_mo_ui()

作用:验证输入 /mo 时,斜杠命令弹窗画面里 /model 排在第一位。

数据流:测试像真人一样输入 /mo → 用测试终端渲染输入框 → 快照应显示 /model 是首选项。

调用关系:它用 tests::type_chars_humanlike 避开粘贴检测干扰,再通过 render 检查弹窗视觉结果。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert_snapshot!, panic!, type_chars_humanlike, new)。

tests::slash_popup_model_first_for_mo_logic7851–7876 ↗
fn slash_popup_model_first_for_mo_logic()

作用:验证输入 /mo 后,内部选中的命令确实是 model,而不只是画面看起来像。

数据流:测试输入 /mo → 读取当前活动弹窗 → 取出被选中的命令 → 断言命令名是 model。

调用关系:它和对应 UI 快照互补:一个看画面,一个看内部选择状态。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, panic!, type_chars_humanlike)。

tests::slash_popup_resume_for_res_ui7879–7904 ↗
fn slash_popup_resume_for_res_ui()

作用:验证输入 /res 时,斜杠命令弹窗画面里 /resume 是首选。

数据流:测试输入 /res → 渲染到测试终端 → 与 slash_popup_res 快照比对。

调用关系:它测试命令匹配排序在 UI 上的结果,防止类似前缀的命令排错。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_snapshot!, type_chars_humanlike, new)。

tests::slash_popup_archive_for_ar_ui7907–7930 ↗
fn slash_popup_archive_for_ar_ui()

作用:验证输入 /ar 时,斜杠命令弹窗显示 archive 相关候选的画面稳定。

数据流:测试输入 /ar → 渲染输入框和弹窗 → 与 slash_popup_ar 快照比对。

调用关系:它通过 type_chars_humanlike 和 render 检查斜杠命令弹窗的可见表现。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_snapshot!, type_chars_humanlike, new)。

tests::slash_popup_resume_for_res_logic7933–7958 ↗
fn slash_popup_resume_for_res_logic()

作用:验证输入 /res 后内部选中的命令是 resume。

数据流:测试输入 /res → 查看活动弹窗 → 获取 selected_item → 断言内置命令名是 resume。

调用关系:它补充 UI 快照,确保回车执行时会执行正确命令。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, panic!, type_chars_humanlike)。

tests::slash_popup_pets_for_pet_ui7961–7984 ↗
fn slash_popup_pets_for_pet_ui()

作用:验证输入 /pet 时,斜杠命令弹窗的画面会把 /pets 放在合适位置。

数据流:测试输入 /pet → 渲染到测试终端 → 和 slash_popup_pet 快照比较。

调用关系:它检查命令补全候选在用户界面上的展示。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_snapshot!, type_chars_humanlike, new)。

tests::slash_popup_pets_for_pet_logic7987–8012 ↗
fn slash_popup_pets_for_pet_logic()

作用:验证输入 /pet 后内部选中的是 pets 命令。

数据流:测试输入 /pet → 读取命令弹窗的选中项 → 确认命令名是 pets。

调用关系:它确保视觉候选和真正会执行的命令一致。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, panic!, type_chars_humanlike)。

tests::slash_popup_btw_for_bt_ui8015–8038 ↗
fn slash_popup_btw_for_bt_ui()

作用:验证输入 /bt 时,斜杠命令弹窗能显示 btw 命令相关候选。

数据流:测试输入 /bt → 渲染输入框 → 与 slash_popup_bt 快照比对。

调用关系:它用快照守住命令搜索结果的展示顺序和样式。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_snapshot!, type_chars_humanlike, new)。

tests::slash_popup_btw_for_bt_logic8041–8066 ↗
fn slash_popup_btw_for_bt_logic()

作用:验证输入 /bt 后内部选中的命令是 btw。

数据流:测试输入 /bt → 进入活动命令弹窗 → 读取选中项 → 断言命令名为 btw。

调用关系:它保证按回车时不会执行别的相近命令。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, panic!, type_chars_humanlike)。

tests::slash_popup_side_for_si_ui8069–8092 ↗
fn slash_popup_side_for_si_ui()

作用:验证输入 /si 时,斜杠命令弹窗能正确展示 side 命令候选。

数据流:测试输入 /si → 渲染终端画面 → 与 slash_popup_si 快照比对。

调用关系:它检查命令候选在终端 UI 里的最终样子。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_snapshot!, type_chars_humanlike, new)。

tests::slash_popup_side_for_si_logic8095–8120 ↗
fn slash_popup_side_for_si_logic()

作用:验证输入 /si 后内部选中的是 side 命令。

数据流:测试输入 /si → 查看活动弹窗的 selected_item → 确认命令名是 side。

调用关系:它和 UI 快照配合,确保命令补全既显示对也执行对。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, panic!, type_chars_humanlike)。

tests::service_tier_slash_command_dispatches_from_catalog_name8123–8152 ↗
fn service_tier_slash_command_dispatches_from_catalog_name()

作用:验证服务档位命令可以按目录里的名字触发,例如输入 /fast 能返回对应的服务档位对象。

数据流:测试启用服务档位命令 → 设置一个 id 为 priority、名字为 fast 的命令 → 输入 /fast 并按回车 → 返回 ServiceTierCommand,内容和设置时一致。

调用关系:它测试普通内置斜杠命令之外的扩展命令分发,确保外部目录命令能接入 composer。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_eq!, type_chars_humanlike, vec!)。

tests::flush_after_paste_burst8154–8157 ↗
fn flush_after_paste_burst(composer: &mut ChatComposer) -> bool

作用:这是测试用的小帮手:等到粘贴突发该结束的时间,再让输入框刷新缓存。

数据流:它接收一个可修改的 ChatComposer → 睡眠推荐的粘贴刷新时长 → 调用 flush_paste_burst_if_due → 返回是否真的刷新了。

调用关系:多个粘贴突发测试会调用它,用稳定等待代替手写时间判断。

调用图:调用 2 个内部函数(flush_paste_burst_if_due, recommended_active_flush_delay);外部调用 1 个(sleep)。

tests::type_chars_humanlike8160–8177 ↗
fn type_chars_humanlike(composer: &mut ChatComposer, chars: &[char])

作用:这是测试用的小帮手:把一串字符按“真人慢慢打字”的节奏送进输入框,避免被误判成粘贴。

数据流:它接收 composer 和字符数组 → 每个字符都调用 handle_key_event 输入 → 等待推荐时间并刷新粘贴缓存 → 如果是空格,还补一个按键释放事件 → 不返回值,只改变输入框状态。

调用关系:很多斜杠命令测试依赖它,因为命令弹窗测试需要模拟正常打字,而不是快速粘贴。

调用图:调用 3 个内部函数(flush_paste_burst_if_due, handle_key_event, recommended_paste_flush_delay);外部调用 4 个(Char, new, new_with_kind, sleep)。

tests::slash_init_dispatches_command_and_does_not_submit_literal_text8180–8226 ↗
fn slash_init_dispatches_command_and_does_not_submit_literal_text()

作用:验证输入 /init 后按回车会执行 init 命令,而不是把“/init”当普通聊天内容发出去。

数据流:测试像真人一样输入 /init → 按回车 → 结果必须是 Command 且命令名为 init → 输入框被清空。

调用关系:它检查斜杠命令分发路径,防止命令文本误走普通提交路径。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, panic!, type_chars_humanlike)。

tests::kill_buffer_persists_after_submit8229–8260 ↗
fn kill_buffer_persists_after_submit()

作用:验证 Ctrl+K 剪切下来的内容,在提交另一条消息后仍然可以用 Ctrl+Y 粘回。

数据流:测试输入 restore me 并把光标放开头 → 按 Ctrl+K 清空并保存到 kill buffer → 输入 hello 并提交 → 再按 Ctrl+Y → restore me 回到输入框。

调用关系:它测试文本编辑器式的剪切缓存不会被提交动作清掉。kill buffer 就像临时剪贴板。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert!, assert_eq!)。

tests::kill_buffer_persists_after_slash_command_dispatch8263–8298 ↗
fn kill_buffer_persists_after_slash_command_dispatch()

作用:验证 Ctrl+K 保存的剪切内容,在执行斜杠命令后也还能恢复。

数据流:测试先把 restore me 剪切进 kill buffer → 输入 /diff 并按回车执行命令 → 输入框清空 → 按 Ctrl+Y → restore me 被粘回。

调用关系:它补充普通提交场景,确保命令执行不会破坏编辑缓存。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, assert!, assert_eq!, panic!)。

tests::slash_command_disabled_while_task_running_keeps_text8301–8342 ↗
fn slash_command_disabled_while_task_running_keeps_text()

作用:验证任务正在运行时,某些斜杠命令不能执行,并且用户输入不会被清掉。

数据流:测试把任务状态设为运行中 → 输入 /review these changes → 按回车 → 返回 None,文本仍在 → 事件通道里出现“任务进行中禁用”的错误提示。

调用关系:它通过 AppEventSender 检查错误消息,保证 composer 阻止命令时也给用户解释原因。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::enter_queues_when_queue_submissions_is_enabled8345–8377 ↗
fn enter_queues_when_queue_submissions_is_enabled()

作用:验证开启“提交排队”后,按回车不会立即提交,而是返回一个排队结果。

数据流:测试打开 queue_submissions → 输入 queued before session → 按回车 → 返回 Queued,动作是普通文本,内容保持一致。

调用关系:它检查会话还没准备好或需要排队时的提交路径。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(new, assert_eq!)。

tests::tab_queues_slash_led_prompts_while_task_running_without_validation8380–8425 ↗
fn tab_queues_slash_led_prompts_while_task_running_without_validation()

作用:验证任务运行时按 Tab 可以把以斜杠开头的输入排队,而且暂时不检查命令是否存在。

数据流:内部小函数为每个输入创建 composer → 设置任务运行中并填入 /compact、/review、/fast、/does-not-exist 等 → 按 Tab → 返回 Queued,动作是 ParseSlash,输入框清空,事件通道没有错误。

调用关系:它测试排队优先的设计:现在不执行也不报错,等以后真正处理队列时再解析斜杠命令。

tests::remapped_submit_does_not_fall_back_to_enter8428–8461 ↗
fn remapped_submit_does_not_fall_back_to_enter()

作用:验证如果用户把“提交”快捷键改成 Ctrl+J,普通 Enter 就不能再偷偷提交。

数据流:测试填入 explain the change → 修改运行时键位,把 submit 设为 Ctrl+J → 按 Enter → 返回 None,文本变成原文加换行。

调用关系:它测试 RuntimeKeymap 对 composer 的影响,保证自定义键位真正覆盖默认行为。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 3 个(new, assert_eq!, vec!)。

tests::remapped_queue_does_not_fall_back_to_tab8464–8494 ↗
fn remapped_queue_does_not_fall_back_to_tab()

作用:验证如果用户把“排队”快捷键改成 Ctrl+Q,Tab 就不能再按旧规则排队。

数据流:测试设置任务运行中并输入 queue me → 把 queue 快捷键改成 Ctrl+Q → 按 Tab → 返回 None,文本仍是 queue me。

调用关系:它保护键位重映射逻辑,避免默认 Tab 行为在用户改键后仍生效。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 3 个(new, assert_eq!, vec!)。

tests::remapped_history_search_does_not_fall_back_to_ctrl_r8497–8522 ↗
fn remapped_history_search_does_not_fall_back_to_ctrl_r()

作用:验证如果历史搜索键改成 F2,Ctrl+R 就不应再打开历史搜索,而 F2 应该可以。

数据流:测试把 history_search_previous 快捷键设为 F2 → 按 Ctrl+R → 历史搜索未开启 → 按 F2 → 历史搜索开启。

调用关系:它检查 composer 的快捷键匹配完全尊重 RuntimeKeymap。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 5 个(Char, F, new, assert!, vec!)。

tests::tab_queues_leading_space_slash_as_plain_text_while_task_running8525–8555 ↗
fn tab_queues_leading_space_slash_as_plain_text_while_task_running()

作用:验证带前导空格的斜杠文本,在任务运行时按 Tab 会当普通文本排队,而不是当命令。

数据流:测试设置任务运行中 → 输入“ /does-not-exist” → 按 Tab → 返回 Queued,文本修剪成 /does-not-exist,但 action 是 Plain。

调用关系:它区分真正从第一个字符开始的斜杠命令和用户故意写的普通文本。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, panic!)。

tests::tab_queues_bang_shell_prompts_while_task_running_without_execution8558–8602 ↗
fn tab_queues_bang_shell_prompts_while_task_running_without_execution()

作用:验证任务运行时,以 ! 开头的 shell 输入按 Tab 只会排队,不会马上执行命令。

数据流:内部小函数创建 composer → 设置任务运行中并填入 !echo hi、! 或带前导空格的 !echo hi → 按 Tab → 返回 Queued,动作是 RunShell,输入框清空且没有立即显示帮助或错误。

调用关系:它保护 shell 命令路径,确保任务忙时不会意外执行用户输入的系统命令。

tests::slash_tab_completion_moves_cursor_to_end8605–8630 ↗
fn slash_tab_completion_moves_cursor_to_end()

作用:验证斜杠命令用 Tab 补全后,光标会移动到补全文本末尾,方便继续输入参数。

数据流:测试输入 /c → 按 Tab → 文本变成 /compact 后跟空格 → 光标位置等于文本长度。

调用关系:它测试命令补全和光标控制的配合,避免补全后用户继续打字插到中间。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, type_chars_humanlike)。

tests::slash_tab_completion_wins_over_queueing_while_task_running8633–8660 ↗
fn slash_tab_completion_wins_over_queueing_while_task_running()

作用:验证任务运行中,如果当前有斜杠命令候选,按 Tab 应先做补全,而不是直接排队。

数据流:测试设置任务运行中 → 输入 /mo → 按 Tab → 返回 None,不排队 → 文本变成 /model 后跟空格,光标在末尾。

调用关系:它明确 Tab 的优先级:有可补全命令时先补全,没有才考虑队列行为。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, type_chars_humanlike)。

tests::slash_key_completes_selected_slash_command_as_text8663–8689 ↗
fn slash_key_completes_selected_slash_command_as_text()

作用:验证在斜杠命令弹窗打开时,再按 / 键会把当前选中的命令补全成文本。

数据流:测试输入 /m → 弹窗选中匹配命令 → 再输入 / → 返回 None → 文本变成 /model 后跟空格,光标在末尾。

调用关系:它测试一个快捷补全手势,让用户可以用 / 键确认当前斜杠命令候选。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(Char, new, assert_eq!, type_chars_humanlike)。

tests::slash_tab_then_enter_dispatches_builtin_command8692–8730 ↗
fn slash_tab_then_enter_dispatches_builtin_command()

作用:确认用户输入斜杠命令前缀后按 Tab 补全,再按回车时,会执行内置命令,而不是把补全后的文字当普通消息发出去。

数据流:测试先新建输入框,模拟输入 /di,再按 Tab 得到 /diff 加空格;接着按 Enter。结果应该产出一个名为 diff 的命令,并清空输入框。

调用关系:这是斜杠命令补全流程的保护测试。它通过 ChatComposer::new 建输入框,用 type_chars_humanlike 模拟真人打字,再把按键交给 handle_key_event 检查最终分发结果。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, panic!, type_chars_humanlike)。

tests::slash_command_elementizes_on_space8733–8752 ↗
fn slash_command_elementizes_on_space()

作用:确认已知斜杠命令在输入空格后,会被标记成一个特殊文本块,而不只是普通字符。

数据流:测试打开协作模式,输入 /plan 和空格;输入框里看起来还是 /plan ,但内部多了一个文本元素,表示 /plan 这段是一个命令占位块。

调用关系:它覆盖斜杠命令从普通输入变成结构化元素的时刻,后续提交命令参数、保留附件元素时会依赖这个标记。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, type_chars_humanlike)。

tests::slash_command_elementizes_only_known_commands8755–8773 ↗
fn slash_command_elementizes_only_known_commands()

作用:确认只有系统认识的斜杠命令才会被做成特殊文本块,像路径一样的 /Users 不会被误判。

数据流:测试输入 /Users 和空格;文字仍保留为 /Users ,但内部文本元素列表为空,说明它只是普通文字。

调用关系:它防止命令识别太贪心。这个测试和上一条形成对照:/plan 能变元素,/Users 不能。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, assert_eq!, type_chars_humanlike)。

tests::slash_command_element_removed_when_not_at_start8776–8801 ↗
fn slash_command_element_removed_when_not_at_start()

作用:确认斜杠命令只有在文本开头才算命令;如果前面插入了别的字符,就会取消命令标记。

数据流:测试先输入 /review 空格,看到它被标记成命令元素;然后把光标移到最前面插入 x,文字变成 x/review ,元素列表被清空。

调用关系:它保护命令识别的位置规则,避免正文中间的 /review 被误当成控制命令。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, assert_eq!, type_chars_humanlike)。

tests::tab_submits_when_no_task_running8804–8829 ↗
fn tab_submits_when_no_task_running()

作用:确认没有任务运行时,普通文字按 Tab 可以直接提交,像一个快捷发送键。

数据流:测试输入 hi,然后按 Tab;结果变成 Submitted,提交文字是 hi,并且输入框被清空。

调用关系:它验证 Tab 在普通输入场景下的行为,和斜杠补全、文件补全、shell 命令场景区分开。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, type_chars_humanlike)。

tests::tab_does_not_submit_for_bang_shell_command8832–8858 ↗
fn tab_does_not_submit_for_bang_shell_command()

作用:确认以感叹号开头的 shell 命令不会因为按 Tab 被误提交。

数据流:测试输入 !ls,再按 Tab;结果是 None,也就是没有提交,输入框仍然保留 !ls 开头的内容。

调用关系:它保护 shell 命令输入。Tab 可能有补全含义,所以这里不能套用普通文字的 Tab 提交规则。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, type_chars_humanlike)。

tests::bang_prefixed_slash_text_submits_literal_shell_command8861–8885 ↗
fn bang_prefixed_slash_text_submits_literal_shell_command()

作用:确认 !/diff 这种以感叹号开头的内容会被当普通 shell 文本提交,而不是触发 /diff 命令。

数据流:测试输入 !/diff 后按回车;输出是普通 Submitted,文本正好是 !/diff。

调用关系:它说明感叹号前缀优先级高于斜杠命令解析,避免用户想执行 shell 文本时被内置命令截走。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, type_chars_humanlike)。

tests::slash_mention_dispatches_command_and_inserts_at8888–8932 ↗
fn slash_mention_dispatches_command_and_inserts_at()

作用:确认 /mention 会作为内置命令触发,并且触发后输入框可以继续插入 @ 符号。

数据流:测试输入 /mention 并按回车;结果是 mention 命令,输入框被清空。随后调用插入字符串,把 @ 放进新的空输入框。

调用关系:它覆盖提及功能入口:先用斜杠命令启动,再让输入框准备好输入 @ 提及内容。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, panic!, type_chars_humanlike)。

tests::slash_plan_args_preserve_text_elements8935–8970 ↗
fn slash_plan_args_preserve_text_elements()

作用:确认 /plan 后面带图片占位符作为参数时,提交命令不会丢掉这个占位符的结构信息。

数据流:测试输入 /plan 空格,附加一张图片;按回车后得到 CommandWithArgs,命令是 plan,参数是图片占位文字,并且参数里的文本元素仍指向这个占位符。

调用关系:它连接斜杠命令、参数解析和图片附件三件事,确保命令参数不是只剩一串普通文字。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 5 个(new, from, assert_eq!, panic!, type_chars_humanlike)。

tests::file_completion_preserves_large_paste_placeholder_elements8973–9026 ↗
fn file_completion_preserves_large_paste_placeholder_elements()

作用:确认大段粘贴生成的占位符,在继续做文件补全后仍能保持正确,提交时再展开成原文。

数据流:测试先粘贴一大段 x,界面显示 [Pasted Content ... chars];再输入 @ma 并用文件搜索结果 Tab 补全为 src/main.rs。提交时,最终文本变成原始大段内容加文件路径,占位元素不再外泄。

调用关系:它保护大粘贴占位符和文件补全的组合场景,防止补全修改文字时破坏占位符映射。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, assert!, assert_eq!, format!, panic!, vec!)。

tests::test_multiple_pastes_submission9031–9107 ↗
fn test_multiple_pastes_submission()

作用:确认多次粘贴时,大段内容会分别用占位符暂存,小段内容直接进入文本,最终提交能按原顺序还原。

数据流:测试依次粘贴大段 x、小段 and、大段 y;界面上看到两个大段占位符夹着 and。按回车后,提交文本变成真实的大段 x、and、大段 y。

调用关系:它覆盖 pending_pastes 这类“暂存的大粘贴内容”列表,确保多个占位符不会互相覆盖。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, new, assert_eq!, panic!)。

tests::test_placeholder_deletion9110–9182 ↗
fn test_placeholder_deletion()

作用:确认删除大粘贴占位符时,对应暂存内容也会一起删除。

数据流:测试创建两个大粘贴占位符,中间夹一段普通文字;先删第一个占位符,暂存数量从 2 变 1;再删第二个,占位符和暂存都清空。

调用关系:它验证退格键和大粘贴暂存表之间的同步,避免界面删了占位符但后台还留着旧内容。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::deleting_duplicate_length_pastes_removes_only_target9187–9224 ↗
fn deleting_duplicate_length_pastes_removes_only_target()

作用:确认两个长度相同的大粘贴内容有相似占位符时,删除其中一个只会删目标那一个。

数据流:测试粘贴两段同样长度内容,第二个占位符带 #2;把光标放末尾退格,只删除第二个,剩下第一个占位符和它的真实内容。

调用关系:它保护占位符去重编号逻辑,防止只按长度匹配导致删错粘贴内容。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, format!)。

tests::large_paste_numbering_continues_with_same_length_placeholder9229–9269 ↗
fn large_paste_numbering_continues_with_same_length_placeholder()

作用:确认相同长度的大粘贴占位符编号不会因为中间删掉一个就乱掉。

数据流:测试先生成 base 和 #2,删除 base 后只剩 #2;再粘贴同样长度的新内容,新占位符变成 #3,而不是重新抢用已有编号。

调用关系:它检查占位符命名的连续性,避免界面中同时出现两个看起来一样的占位符。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, format!)。

tests::large_paste_numbering_reuses_after_all_deleted9274–9308 ↗
fn large_paste_numbering_reuses_after_all_deleted()

作用:确认所有同类大粘贴占位符都删完后,编号可以从基础名字重新开始。

数据流:测试粘贴一段大内容得到基础占位符,退格删掉后暂存为空;再粘贴同样内容,又得到基础占位符,而不是 #2。

调用关系:它补足编号规则的另一半:有残留时继续编号,没有残留时干净重来。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, format!)。

tests::test_partial_placeholder_deletion9311–9360 ↗
fn test_partial_placeholder_deletion()

作用:确认用户只删掉占位符的一部分时,系统会放弃这条占位符映射,避免之后错误展开。

数据流:测试把光标放在占位符中间或末尾退格;占位符不再完整存在,pending_pastes 数量变成 0。

调用关系:它处理手动编辑占位符的危险情况。占位符像取件码,被改坏后就不能再用来取回原始大粘贴。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, format!)。

tests::attach_image_and_submit_includes_local_image_paths9364–9399 ↗
fn attach_image_and_submit_includes_local_image_paths()

作用:确认附加本地图片后提交,文字里有图片占位符,图片路径也能被取出来。

数据流:测试附加 /tmp/image1.png,再粘贴 hi;提交后文本是 [Image #1] hi,文本元素标出图片占位符范围,随后能取到这张图片路径。

调用关系:它覆盖图片附件的基本提交流程:attach_image 放入占位符,Enter 产出提交结果,take_recent_submission_images 交出真实图片路径。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, from, assert_eq!, panic!)。

tests::submit_captures_recent_mention_bindings_before_clearing_textarea9402–9433 ↗
fn submit_captures_recent_mention_bindings_before_clearing_textarea()

作用:确认提交时会先保存最近一次提及绑定,再清空输入框。

数据流:测试设置文本 $figma please,并给 $figma 绑定一个文件路径;提交后能从最近提交记录里取回绑定,而当前输入框的绑定已清空。

调用关系:它保护提及功能的数据交接顺序,避免清空输入框时把刚提交的提及信息也一起丢了。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, new, assert!, assert_eq!, vec!)。

tests::history_navigation_restores_remote_and_local_image_attachments9436–9467 ↗
fn history_navigation_restores_remote_and_local_image_attachments()

作用:确认翻历史记录时,既能恢复远程图片地址,也能恢复本地图片附件。

数据流:测试先设置远程图片 URL,再附加本地图片并提交;清空当前状态后按上箭头,输入框恢复图片占位符,本地路径和远程 URL 都回来了。

调用关系:它验证历史记录不仅存文字,也存附件状态。没有这个,用户翻回旧消息时图片会丢。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(new, from, new, new, assert!, assert_eq!, vec!)。

tests::history_navigation_restores_remote_only_submissions9470–9499 ↗
fn history_navigation_restores_remote_only_submissions()

作用:确认只有远程图片、没有文字的提交,也能被历史记录恢复。

数据流:测试设置两个远程图片 URL,准备一次空文本提交;清空后按上箭头,文本仍为空,但远程图片列表恢复为原来的两个地址。

调用关系:它覆盖“无文字但有附件”的特殊历史项,避免历史系统只按文本是否为空来判断是否记录。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, new, new, assert!, assert_eq!, vec!)。

tests::history_navigation_leaves_cursor_at_end_of_line9502–9554 ↗
fn history_navigation_leaves_cursor_at_end_of_line()

作用:确认用上下键翻历史后,光标会停在文本末尾,方便继续编辑。

数据流:测试提交 first 和 second;按上键依次回到 second、first,按下键回到 second、空草稿,每次光标都在当前文本末尾。

调用关系:它检查普通编辑模式下的历史导航细节,让用户翻出旧输入后不用再手动把光标移到末尾。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, type_chars_humanlike)。

tests::vim_normal_j_k_navigate_history_at_history_boundaries9557–9602 ↗
fn vim_normal_j_k_navigate_history_at_history_boundaries()

作用:确认开启 Vim 模式后,普通模式下的 k 和 j 可以像上下键一样翻历史,并在边界处表现正常。

数据流:测试提交 first 和 second,开启 Vim;按 k 回到 second、再到 first,按 j 回到 second、再到空草稿,光标停在 Vim 普通模式习惯的位置。

调用关系:它把 Vim 键位接入同一套历史导航流程,同时检查到最旧和最新位置时不会越界出错。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, assert!, assert_eq!, type_chars_humanlike)。

tests::remapped_vim_normal_history_navigation_does_not_fall_back_to_j_k9605–9638 ↗
fn remapped_vim_normal_history_navigation_does_not_fall_back_to_j_k()

作用:确认用户把 Vim 普通模式的上下移动改成 F2/F3 后,旧的 j/k 不会偷偷继续翻历史。

数据流:测试提交一条历史,把 Vim 上下键位改成 F2 和 F3;按 k 没反应,按 F2 才恢复 first,按 F3 回到空草稿。

调用关系:它保护自定义快捷键配置。RuntimeKeymap 改了规则后,ChatComposer 必须尊重配置,而不是保留默认后门。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 7 个(Char, F, new, assert!, assert_eq!, type_chars_humanlike, vec!)。

tests::vim_normal_j_k_fall_back_to_multiline_cursor_movement9641–9663 ↗
fn vim_normal_j_k_fall_back_to_multiline_cursor_movement()

作用:确认 Vim 模式下如果当前是多行文本,j/k 可以用于行间移动,而不是总是翻历史。

数据流:测试把文本设成 one 换行 two,光标在开头;按 j 光标移到下一行开头,按 k 回到第一行开头。

调用关系:它区分“编辑多行文本”和“翻历史”两种场景,避免用户在多行输入里一按 j/k 就被带到历史记录。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(Char, new, assert_eq!)。

tests::vim_normal_operator_motion_does_not_navigate_history9666–9696 ↗
fn vim_normal_operator_motion_does_not_navigate_history()

作用:确认 Vim 的操作符加移动键,比如 d 后再 k,不会被误当成历史导航。

数据流:测试先用 k 翻到 second;再按 d 进入操作符等待状态,接着按 k。结果输入框清空,说明这次 k 被 Vim 编辑命令消费了,没有继续翻到 first。

调用关系:它保护 Vim 语义。ChatComposer 要知道 k 有时是历史键,有时是 Vim 删除命令的一部分。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, assert!, assert_eq!, type_chars_humanlike)。

tests::vim_normal_operator_pending_consumes_submit_key9699–9725 ↗
fn vim_normal_operator_pending_consumes_submit_key()

作用:确认 Vim 操作符等待状态下按 Enter 不会提交消息,而是取消这个等待状态。

数据流:测试设置 hello,开启 Vim,按 d 进入操作符等待;再按 Enter,结果是 None,文本仍是 hello,模式回到 Vim: Normal,等待状态被清掉。

调用关系:它避免用户在 Vim 命令没打完时误发送消息,属于提交键和编辑模式之间的安全保护。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, new, assert!, assert_eq!)。

tests::remapped_editor_history_navigation_does_not_fall_back_to_up9728–9756 ↗
fn remapped_editor_history_navigation_does_not_fall_back_to_up()

作用:确认普通编辑模式的历史上翻键被改掉后,原来的 Up 键不会继续生效。

数据流:测试提交 first,把 editor.move_up 改成 F2;按 Up 仍为空,按 F2 才恢复 first。

调用关系:它和 Vim 重映射测试对应,检查非 Vim 编辑模式也完全尊重用户键位配置。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 6 个(F, new, assert!, assert_eq!, type_chars_humanlike, vec!)。

tests::history_navigation_from_start_of_bang_command_recalls_older_entry9759–9792 ↗
fn history_navigation_from_start_of_bang_command_recalls_older_entry()

作用:确认在 ! 命令历史项开头按上键时,可以继续翻到更旧的历史。

数据流:测试提交 first,再提交 !git;第一次上键回到 !git,把光标移到开头后再上键,恢复 first。

调用关系:它处理 shell 命令历史的边界:光标在开头时,上键表示继续找上一条,而不是在当前命令里移动。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, type_chars_humanlike)。

tests::vim_normal_history_navigation_from_start_of_bang_command_recalls_older_entry9795–9827 ↗
fn vim_normal_history_navigation_from_start_of_bang_command_recalls_older_entry()

作用:确认 Vim 模式下,! 命令历史也能用 k 继续翻到更旧条目。

数据流:测试提交 first 和 !git,开启 Vim;第一次 k 回到 !git,第二次 k 回到 first,并检查光标位置符合 Vim 普通模式。

调用关系:它是上一条普通模式行为的 Vim 版本,保证不同键盘习惯下历史规则一致。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, assert!, assert_eq!, type_chars_humanlike)。

tests::set_text_content_reattaches_images_without_placeholder_metadata9830–9852 ↗
fn set_text_content_reattaches_images_without_placeholder_metadata()

作用:确认恢复文本内容时,即使图片文本元素没有保存占位符名字,也能重新挂回本地图片路径。

数据流:测试构造带 [Image #1] restored 的文本和一个没有 placeholder 元数据的文本元素,再传入图片路径;设置完成后,本地图片路径列表包含这张图片。

调用关系:它保护旧数据或不完整元数据的兼容性,让历史恢复、外部设置文本时不会因为少一个字段就丢图片。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 4 个(from, assert_eq!, format!, vec!)。

tests::large_paste_preserves_image_text_elements_on_submit9855–9895 ↗
fn large_paste_preserves_image_text_elements_on_submit()

作用:确认大段粘贴展开后,后面的图片占位符范围仍然算得准。

数据流:测试先大粘贴,再加空格和图片;提交后文本是完整大内容加 [Image #1],图片文本元素的起止位置落在展开后的正确位置。

调用关系:它检查大粘贴占位符替换成真实内容后,图片元素的坐标会随之平移,不会指到错误位置。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, from, assert_eq!, format!, panic!)。

tests::large_paste_with_leading_whitespace_trims_and_shifts_elements9898–9938 ↗
fn large_paste_with_leading_whitespace_trims_and_shifts_elements()

作用:确认大粘贴内容提交时如果会去掉首尾空白,图片元素的位置也会跟着调整。

数据流:测试粘贴前面带两个空格的大内容,再附图片;提交后文本使用 trim 后的大内容,图片占位符仍有正确的字节范围。

调用关系:它保护“清理文本”和“保留附件位置”同时发生的场景,防止删掉空格后元素偏移没更新。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, from, assert_eq!, format!, panic!)。

tests::pasted_crlf_normalizes_newlines_for_elements9941–9981 ↗
fn pasted_crlf_normalizes_newlines_for_elements()

作用:确认粘贴 Windows 风格换行 CRLF 时,提交内容会统一成普通换行,并且图片元素位置正确。

数据流:测试粘贴 line1\r\nline2\r\n,再附图片;提交文本变成 line1\nline2\n 加图片,没有 \r,图片元素范围按规范化后的文本计算。

调用关系:它处理跨平台粘贴差异。不同系统换行写法不同,统一后才能让元素位置和显示一致。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, from, assert!, assert_eq!, panic!)。

tests::suppressed_submission_restores_pending_paste_payload9984–10034 ↗
fn suppressed_submission_restores_pending_paste_payload()

作用:确认一次提交被拦下时,大粘贴暂存内容不会丢,之后正常提交还能展开。

数据流:测试把文本设成 /unknown 加大粘贴;第一次回车因为未知斜杠命令被抑制,输入框仍保留占位符和暂存。随后在开头插入空格让它变普通文本,再回车,提交文本展开成真实大粘贴内容。

调用关系:它保护失败提交后的恢复能力。即使命令解析拒绝发送,也不能把用户刚粘贴的大段内容弄丢。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::attach_image_without_text_submits_empty_text_and_images10037–10073 ↗
fn attach_image_without_text_submits_empty_text_and_images()

作用:确认只附图片、不输入文字时,也能正常提交图片消息。

数据流:测试附加 /tmp/image2.png 后直接回车;提交文本是 [Image #1],文本元素标记图片范围,最近提交图片列表里有这张图,当前附件列表被清空。

调用关系:它覆盖纯图片提交流程,避免系统把“没有普通文字”误判成空消息而不发。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, from, assert!, assert_eq!, panic!)。

tests::duplicate_image_placeholders_get_suffix10076–10102 ↗
fn duplicate_image_placeholders_get_suffix()

作用:确认连续附加多张图片时,占位符编号不同,不会都叫同一个名字。

数据流:测试附一张图,插入空格,再附同一张图;输入框同时包含 [Image #1] 和 [Image #2],附件列表里的占位符也分别对应。

调用关系:它保证图片附件的占位符唯一性,后续删除、提交、映射路径都靠这个区分。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(from, assert!, assert_eq!)。

tests::image_placeholder_backspace_behaves_like_text_placeholder10105–10141 ↗
fn image_placeholder_backspace_behaves_like_text_placeholder()

作用:确认图片占位符用退格删除时,行为像一个整体占位块,而不是普通散字。

数据流:测试先附图片,在占位符末尾退格,图片占位符和附件记录一起删除;再重新附图,把光标放到占位符中间退格,占位符仍保留,附件也还在。

调用关系:它让图片占位符像一个“贴纸”一样整体存在:从边界删可以拿掉,随便从中间破坏则不应误删附件。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, from, assert!, assert_eq!, panic!)。

tests::backspace_with_multibyte_text_before_placeholder_does_not_panic10144–10171 ↗
fn backspace_with_multibyte_text_before_placeholder_does_not_panic()

作用:确认图片占位符旁边有日文等多字节字符时,退格不会崩溃,也不会误删图片。

数据流:测试先插入图片占位符,再输入 日本語;在末尾退格删除最后一个字符后,附件列表仍有图片,文本仍以 [Image #1] 开头。

调用关系:它保护 Unicode 文本处理。中文、日文这类字符在底层占多个字节,退格位置算错很容易出错。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, from, assert!, assert_eq!)。

tests::deleting_one_of_duplicate_image_placeholders_removes_one_entry10174–10227 ↗
fn deleting_one_of_duplicate_image_placeholders_removes_one_entry()

作用:确认删除多张图片中的一张后,只移除对应附件,并把剩下的图片重新编号。

数据流:测试附两张图片,中间有空格;删除第一张的占位符后,文本里只剩一个 [Image #1],原来的第二张图片路径还在附件列表中,并被重新标成 [Image #1]。

调用关系:它保护图片删除和重新编号流程。用户删掉前面的图片后,界面编号要变整齐,后台路径也必须跟着正确改。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, from, assert_eq!)。

tests::deleting_reordered_image_one_renumbers_text_in_place10230–10290 ↗
fn deleting_reordered_image_one_renumbers_text_in_place()

作用:这个测试确认:就算图片占位符在文字里被用户调换了顺序,删除第一张图片后,剩下图片也会被重新编号。它防止出现“文字里写 Image #2,但实际只剩一张图”的混乱。

数据流:测试先造出一个输入框,放入两张本地图片和一段顺序被打乱的文字。然后把光标放到第一张图片占位符后面,模拟按退格键删除它。最后检查文字变成只剩一个 Image #1,附件列表里也只剩第二张图片,并且它的标签被改成 Image #1。

调用关系:这是测试运行器调用的用例。它直接驱动 ChatComposer 的文本设置和按键处理流程,用来验证删除图片后,文本框内容和附件清单会一起更新,而不是各改各的。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 5 个(new, from, assert_eq!, format!, vec!)。

tests::deleting_first_text_element_renumbers_following_text_element10293–10329 ↗
fn deleting_first_text_element_renumbers_following_text_element()

作用:这个测试确认:两个图片占位符挨在一起时,删除第一个,第二个会自动变成新的第一个。它防止相邻“不可拆的小块文字”删除后编号断档。

数据流:测试先创建输入框,连续附加两张图片,文本里出现 Image #1 和 Image #2。接着把光标放到开头,模拟按 Delete 删除第一个元素。结果应该是附件只剩第二张图,文本和附件里的占位符都变成 Image #1。

调用关系:这是对普通文本编辑路径的保护测试。它不走专门的“删除附件按钮”,而是模拟用户在文本框里按删除键,确认底层编辑动作仍会通知附件编号重排。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, from, assert_eq!)。

tests::pasting_filepath_attaches_image10332–10355 ↗
fn pasting_filepath_attaches_image()

作用:这个测试确认:用户粘贴一个真实图片文件路径时,输入框会把它当作图片附件,而不是普通文字。这样用户复制图片路径后可以直接发送图片。

数据流:测试先临时生成一个 PNG 图片文件,再把这个文件路径粘贴进输入框。输入框处理粘贴后返回需要重画界面,文本开头出现 Image #1,占用最近提交图片列表的路径也正是这个临时文件。

调用关系:这是粘贴处理流程的测试。它通过 ChatComposer 的 handle_paste 入口,检查文件路径识别、图片附加、界面刷新提示和后续提交图片收集是否串在一起。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(from_fn, assert!, assert_eq!, tempdir)。

tests::slash_path_input_submits_without_command_error10358–10392 ↗
fn slash_path_input_submits_without_command_error()

作用:这个测试确认:像 /Users/example 这样的 Unix 路径不会被误判成斜杠命令。否则用户提交路径时会莫名其妙触发命令错误。

数据流:测试把输入框内容设成一个以斜杠开头的文件路径,然后模拟按 Enter。结果应该是正常提交这段路径文本,输入框被清空,并且事件通道里没有额外的错误或命令事件。

调用关系:这是提交判断流程的边界测试。它检查 ChatComposer 在 Enter 时会区分“命令”和“普通路径文本”,并确保不会把工作交给命令处理分支。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::slash_with_leading_space_submits_as_text10395–10429 ↗
fn slash_with_leading_space_submits_as_text()

作用:这个测试确认:前面带空格的“/xxx”应该作为普通文本提交,而不是命令。它保护用户故意输入类似命令文字时不被系统抢走。

数据流:测试把输入框设成带前导空格的斜杠文本,然后按 Enter。提交结果里的文字会去掉外层空白,得到 /this-looks-like-a-command;输入框清空,事件通道没有收到意外事件。

调用关系:这是命令识别规则的测试。它说明只有真正位于命令位置、符合规则的输入才走斜杠命令路径,普通消息仍走正常提交路径。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::pending_first_ascii_char_flushes_as_typed10434–10458 ↗
fn pending_first_ascii_char_flushes_as_typed()

作用:这个测试确认:输入的第一个普通 ASCII 字符如果被暂时当成“可能是粘贴”的开头,过一小会儿也会正常显示出来。它防止用户打第一个字后永远看不到。

数据流:测试模拟按下 h。输入框先进入粘贴爆发检测状态,文本暂时为空。等待推荐的刷新时间后,调用刷新函数,h 被写入文本框,粘贴爆发状态结束。

调用关系:这是快速粘贴识别机制的安全网。它验证 ChatComposer 不会因为要识别粘贴,就牺牲普通打字的可见性。

调用图:调用 3 个内部函数(new, new, recommended_paste_flush_delay);外部调用 5 个(Char, new, assert!, assert_eq!, sleep)。

tests::burst_paste_fast_small_buffers_and_flushes_on_stop10463–10509 ↗
fn burst_paste_fast_small_buffers_and_flushes_on_stop()

作用:这个测试确认:一小段非常快的输入会先缓存,等输入停下来后一次性放进文本框。这样可以把“粘贴”跟“手打”区分开,同时小内容仍直接显示原文。

数据流:测试用很短的时间间隔连续送入 32 个 a。过程中输入框保持空白,表示内容还在缓存。等超过刷新等待时间后,缓存被写入文本框,得到 32 个 a,并且没有生成大段粘贴占位符。

调用关系:这是 PasteBurst 这套“粘贴爆发”判断的测试。它通过带时间的输入入口喂数据,再通过刷新入口收尾,验证小粘贴不会被替换成占位说明。

调用图:调用 3 个内部函数(new, new, recommended_active_flush_delay);外部调用 6 个(from_millis, now, Char, new, assert!, assert_eq!)。

tests::burst_paste_fast_large_inserts_placeholder_on_flush10514–10548 ↗
fn burst_paste_fast_large_inserts_placeholder_on_flush()

作用:这个测试确认:超大段快速粘贴不会直接塞满输入框,而是显示一个简短占位符。这样界面不会被几千字刷屏,真实内容仍被保存。

数据流:测试快速输入超过阈值数量的 x。停止前文本框为空;到刷新时间后,文本框显示类似“Pasted Content N chars”的占位文字,pending_pastes 里保存占位符和完整原文。

调用关系:这是大粘贴保护机制的测试。它验证 ChatComposer 会把展示用的短文字和真正要提交的长文本分开保存。

调用图:调用 3 个内部函数(new, new, recommended_active_flush_delay);外部调用 7 个(from_millis, now, Char, new, assert!, assert_eq!, format!)。

tests::humanlike_typing_1000_chars_appears_live_no_placeholder10553–10570 ↗
fn humanlike_typing_1000_chars_appears_live_no_placeholder()

作用:这个测试确认:即使用户手动慢慢输入很多字符,也应该实时显示原文,而不是被当成大粘贴折叠成占位符。

数据流:测试创建 1000 个 z,并用模拟“像人一样打字”的辅助过程输入。最后文本框里就是完整的 1000 个 z,待展开粘贴列表为空。

调用关系:这是粘贴识别和正常打字之间的分界测试。它说明系统看的是输入速度和模式,不只是字符数量。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(assert!, assert_eq!, type_chars_humanlike, vec!)。

tests::slash_popup_not_activated_for_slash_space_text_history_like_input10573–10602 ↗
fn slash_popup_not_activated_for_slash_space_text_history_like_input()

作用:这个测试确认:“/ 后面有空格再跟文字”的历史内容不会打开斜杠命令弹窗。否则用户翻历史时可能被命令菜单打断。

数据流:测试把输入框内容设为 / test。设置内容后弹窗同步逻辑会运行,但活动弹窗应保持为空。随后按 Up,结果也是普通无操作,不进入命令弹窗处理。

调用关系:这是历史文本和斜杠弹窗互动的测试。它确保 set_text_content 触发的弹窗同步不会把旧消息误认为正在输入命令。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, new, assert!, assert_eq!)。

tests::slash_popup_activated_for_bare_slash_and_valid_prefixes10605–10648 ↗
fn slash_popup_activated_for_bare_slash_and_valid_prefixes()

作用:这个测试确认:用户输入裸斜杠或能匹配命令的前缀时,会打开命令弹窗;完全匹配不到的前缀则不会打开。

数据流:测试依次把内容设成 /、/re、/ac 和 /zzz。前三种都应让活动弹窗变成命令弹窗,最后一种因为匹配不到内置命令,活动弹窗保持为空。

调用关系:这是斜杠命令提示入口的测试。它验证 ChatComposer 根据当前文字同步弹窗时,会用前缀或模糊匹配来决定是否展示命令列表。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(new, assert!)。

tests::bare_slash_command_can_be_recalled_after_recording_pending_history10651–10673 ↗
fn bare_slash_command_can_be_recalled_after_recording_pending_history()

作用:这个测试确认:直接输入完整斜杠命令并执行后,记录到历史里,之后按上箭头能找回来。

数据流:测试输入 /diff,按 Enter 得到 Diff 命令结果。随后记录待写入的命令历史,再按 Up,输入框重新显示 /diff。

调用关系:这是命令提交和历史召回的衔接测试。它验证执行命令后,还需要显式记录历史,历史导航才能把它恢复到输入框。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, new, assert_eq!)。

tests::popup_selected_slash_command_records_canonical_command_history10676–10698 ↗
fn popup_selected_slash_command_records_canonical_command_history()

作用:这个测试确认:用户只输入命令缩写并从弹窗选中命令后,历史里记录的是完整标准命令,而不是缩写。

数据流:测试输入 /di,按 Enter 后识别为 Diff 命令。记录历史后按 Up,输入框显示的是 /diff,而不是 /di。

调用关系:这是命令弹窗选择和历史记录之间的测试。它保证历史里存的是用户下次能直接执行的规范写法。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, new, assert_eq!)。

tests::inline_slash_command_can_be_recalled_after_recording_pending_history10701–10732 ↗
fn inline_slash_command_can_be_recalled_after_recording_pending_history()

作用:这个测试确认:带参数的行内斜杠命令,比如 /plan investigate this,执行后也能完整进入历史并被召回。

数据流:测试先开启协作模式,再输入 /plan investigate this,并关闭弹窗干扰。按 Enter 后得到 Plan 命令和参数 investigate this;记录历史后按 Up,当前文本恢复成完整命令行。

调用关系:这是带参数命令的提交和历史测试。它验证 ChatComposer 不只记录命令名,也保留用户跟在命令后面的说明文字。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。

tests::apply_external_edit_rebuilds_text_and_attachments10735–10773 ↗
fn apply_external_edit_rebuilds_text_and_attachments()

作用:这个测试确认:从外部编辑器改回来的文本,会重新建立输入框文字和图片附件关系,同时清掉旧的大粘贴占位记录。

数据流:测试先放入一个图片占位符、一个图片附件和一个待展开粘贴记录。然后应用外部编辑后的新文本。结果当前文字变成新内容,待粘贴记录清空,图片附件仍保留,光标移动到文本末尾。

调用关系:这是外部编辑回写流程的测试。它检查 apply_external_edit 会重建文本框内部元素,而不是简单覆盖字符串导致附件丢失或残留脏状态。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 4 个(from, assert!, assert_eq!, format!)。

tests::apply_external_edit_absorbs_bash_prefix_without_duplication10776–10793 ↗
fn apply_external_edit_absorbs_bash_prefix_without_duplication()

作用:这个测试确认:外部编辑返回带 ! 的 bash 命令时,输入框不会把感叹号重复显示。这里的 bash 模式指“把输入当作 shell 命令”的模式。

数据流:测试先让输入框处于 !git status 这种 bash 模式,再应用同样的外部编辑文本。结果内部文本框只存 git status,bash 模式为真,但 current_text 对外仍显示 !git status。

调用关系:这是外部编辑和 bash 模式的兼容测试。它保证“模式标记”和“实际文本”各司其职,不会出现 !!git status 之类重复前缀。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::apply_external_edit_can_leave_bash_mode10796–10813 ↗
fn apply_external_edit_can_leave_bash_mode()

作用:这个测试确认:如果外部编辑把开头的 ! 去掉,输入框会退出 bash 模式。

数据流:测试先设置 !git status,再应用不带感叹号的 git status。结果 bash 模式关闭,文本框和当前文本都变成普通的 git status。

调用关系:这是外部编辑切换模式的测试。它说明 apply_external_edit 不只是改文字,也会根据新文字重新判断输入模式。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::apply_external_edit_can_enter_bash_mode10816–10833 ↗
fn apply_external_edit_can_enter_bash_mode()

作用:这个测试确认:如果外部编辑加上了开头的 !,输入框会进入 bash 模式。

数据流:测试先设置普通 git status,再应用 !git status。结果 bash 模式打开,内部文本框保存 git status,对外当前文本显示 !git status。

调用关系:这是上一条的反向测试。它保证外部编辑器可以把一条普通消息改成 shell 命令,ChatComposer 会正确吸收这个变化。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert!, assert_eq!)。

tests::apply_external_edit_drops_missing_attachments10836–10858 ↗
fn apply_external_edit_drops_missing_attachments()

作用:这个测试确认:外部编辑后的文本如果删掉了图片占位符,对应图片附件也会被移除。

数据流:测试先放入一个图片占位符和附件。然后应用没有任何图片占位符的新文本。结果当前文本是 No images here,附件列表为空。

调用关系:这是附件清理测试。它验证 apply_external_edit 会用新文本作为准绳,删除已经不再被文字引用的图片。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 3 个(from, assert!, assert_eq!)。

tests::apply_external_edit_renumbers_image_placeholders10861–10892 ↗
fn apply_external_edit_renumbers_image_placeholders()

作用:这个测试确认:外部编辑只保留第二张图时,它会被重新编号成第一张图。这样用户看到的图片编号始终连续。

数据流:测试先附加两张本地图片,然后外部编辑后的文本只保留原来的 Image #2。应用后,文本变成 Keep Image #1,附件只剩第二张图片,元素载荷也更新成 Image #1。

调用关系:这是外部编辑后的图片重编号测试。它把 attach_image、apply_external_edit、local_image_paths 和文本元素检查串起来,确认所有视图都同意新的编号。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 3 个(from, assert_eq!, format!)。

tests::current_text_with_pending_expands_placeholders10895–10918 ↗
fn current_text_with_pending_expands_placeholders()

作用:这个测试确认:对外取“带真实粘贴内容的当前文本”时,大粘贴占位符会被展开成原文。

数据流:测试把文本框里插入一个 Pasted Content 占位符,并在 pending_pastes 里登记它对应 hello。调用 current_text_with_pending 后,返回值不是占位符,而是 hello。

调用关系:这是提交前文本恢复的测试。它验证展示层为了简洁使用占位符,但真正发送或处理时可以还原完整内容。

调用图:调用 2 个内部函数(new, new);外部调用 1 个(assert_eq!)。

tests::current_text_with_pending_expands_overlapping_placeholders10921–10945 ↗
fn current_text_with_pending_expands_overlapping_placeholders()

作用:这个测试确认:多个大粘贴占位符文字彼此相似甚至有重叠时,也能展开成各自正确的原文。

数据流:测试连续粘贴两段很长的内容,一段全是 a,一段全是 b。界面当前文本显示两个占位符;调用带 pending 展开的文本后,得到第一段完整 a 加第二段完整 b。

调用关系:这是占位符替换算法的防混淆测试。它防止因为两个占位符长得像,就把第二段替换错或漏替换。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, format!)。

tests::apply_external_edit_limits_duplicates_to_occurrences10948–10973 ↗
fn apply_external_edit_limits_duplicates_to_occurrences()

作用:这个测试确认:如果外部编辑文本里同一个图片占位符出现两次,附件列表不会错误复制成两份。

数据流:测试先放入一张图片和一个占位符。外部编辑后文本包含两次同样的占位符。结果当前文本保留这两处文字,但附件列表仍只有一张图。

调用关系:这是外部编辑处理重复占位符的测试。它保证附件按真实图片来算,不按占位文字出现次数盲目复制。

调用图:调用 3 个内部函数(local_image_label_text, new, new);外部调用 3 个(from, assert_eq!, format!)。

tests::remote_images_do_not_modify_textarea_text_or_elements10976–10994 ↗
fn remote_images_do_not_modify_textarea_text_or_elements()

作用:这个测试确认:设置远程图片 URL 不会往文本框里插入占位文字,也不会创建文本元素。远程图片像“附在消息旁边的附件”,不是用户手写内容的一部分。

数据流:测试给输入框设置两个远程图片地址。之后当前文本仍为空,文本元素列表也为空。

调用关系:这是远程图片和文本框隔离的测试。它说明 set_remote_image_urls 只改附件状态,不动用户可编辑的正文。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, vec!)。

tests::attach_image_after_remote_prefix_uses_offset_label10997–11019 ↗
fn attach_image_after_remote_prefix_uses_offset_label()

作用:这个测试确认:已经有远程图片时,再添加本地图片,本地图片编号会接在远程图片后面。

数据流:测试先设置两个远程图片 URL,再附加一张本地图片。结果本地附件的占位符是 Image #3,当前文本也显示 Image #3。

调用关系:这是远程和本地图片编号合并规则的测试。它保证用户看到的一组图片编号是统一连续的,而不是本地图片又从 1 开始。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(from, assert_eq!, vec!)。

tests::prepare_submission_keeps_remote_offset_local_placeholder_numbering11022–11056 ↗
fn prepare_submission_keeps_remote_offset_local_placeholder_numbering()

作用:这个测试确认:准备提交时,如果远程图片占了前面的编号,本地图片的占位符编号要保持偏移后的数字。

数据流:测试先设置一个远程图片,再设置正文为 Image #2 hello,并登记一个本地图片元素。准备提交后,提交文本仍是 Image #2 hello,提交的文本元素也保留 Image #2。

调用关系:这是提交整理流程的测试。它验证 prepare_submission_text 不会在有远程图片时错误地把本地图片重编号回 Image #1。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_eq!, vec!)。

tests::prepare_submission_with_only_remote_images_returns_empty_text11059–11076 ↗
fn prepare_submission_with_only_remote_images_returns_empty_text()

作用:这个测试确认:只有远程图片、没有正文时,也可以生成一次提交,而且文本内容为空是合法的。

数据流:测试设置一个远程图片 URL,然后准备提交。返回的提交文本是空字符串,文本元素也是空列表。

调用关系:这是纯图片提交场景的测试。它保证 prepare_submission_text 不会因为输入框没有文字就误以为没有任何可提交内容。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, assert_eq!, vec!)。

tests::delete_selected_remote_image_relabels_local_placeholders11079–11117 ↗
fn delete_selected_remote_image_relabels_local_placeholders()

作用:这个测试确认:删除远程图片后,本地图片的编号会跟着前移。否则远程图片少了,本地占位符还会显示旧编号。

数据流:测试先设置两张远程图片,再附加一张本地图片,此时本地是 Image #3。通过上箭头选中远程图片并按 Delete 删除后,远程列表减少,本地文本改成 Image #2;再删一张远程图后,本地又改成 Image #1。

调用关系:这是附件选择删除和编号重排的联动测试。它模拟用户用键盘选中远程图片删除,确认远程附件列表、本地附件占位符和文本框显示同步变化。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, from, assert_eq!, vec!)。

tests::input_disabled_ignores_keypresses_and_hides_cursor11120–11155 ↗
fn input_disabled_ignores_keypresses_and_hides_cursor()

作用:这个测试确认:输入被禁用时,按键不会改变内容,光标也不会显示。这样在系统不允许输入时,用户不会误以为还能编辑。

数据流:测试先设置文本 hello,再关闭输入并给一个提示。随后模拟按 x,结果返回无动作、不需要重画,当前文本仍是 hello;询问光标位置时返回 None。

调用关系:这是禁用输入状态的测试。它验证 handle_key_event 和 cursor_pos 都尊重 input_enabled 这个开关。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, new, new, assert!, assert_eq!)。

提及和文件搜索弹窗流程

此组从插件和技能中汇总提及候选项,管理搜索模式和过滤,并驱动由实时文件搜索支持的统一提及弹窗。

tui/src/app/plugin_mentions.rs源码 ↗
domain_logicrefresh_plugin_mentions 时活跃,用于刷新 TUI 的插件提及候选项

TUI 是文字界面,也需要像图形界面一样支持“提到插件”。但服务器给的是一整份插件清单,里面有市场、插件、安装状态、启用状态、展示名、简介等信息,不是直接给“可提到的插件”。这个文件就像一个筛选和改名的小柜台:先向 app-server 请求插件列表,再逐个市场、逐个插件检查。只有已经安装、已经启用,并且没有被管理员禁用的插件,才会变成 PluginCapabilitySummary,也就是“提及候选项”。展示名优先用插件界面里写好的 display_name;如果没有,就用插件自己的名字。简介优先用插件短描述;如果没有,就用插件市场名兜底。这里还特意把技能、MCP 服务器、应用连接器这些字段留空,因为当前接口只提供插件级别的提及信息,还没有更细的会话能力总结。

函数细节8
fetch_plugin_mentions14–20 ↗
async fn fetch_plugin_mentions(
    request_handle: AppServerRequestHandle,
    cwd: PathBuf,
) -> Result<Vec<PluginCapabilitySummary>>

作用:这个函数负责真正去服务器拿插件列表,然后把列表变成 TUI 可用的“插件提及候选项”。别人想刷新 @ 插件列表时,会从这里开始。

数据流:进去的是一个 app-server 请求句柄和当前工作目录 cwd;它先调用 request_plugin_list 去服务器拿插件清单;拿到 PluginListResponse 后交给 plugin_mentions_from_list_response 做筛选和转换;最后出来的是一组 PluginCapabilitySummary。如果请求服务器失败,它会把错误原样返回。

调用关系:它由 refresh_plugin_mentions 调用,处在“刷新插件提及功能”的入口位置。它自己不做复杂筛选,而是先把联网取列表的活交给 request_plugin_list,再把整理列表的活交给 plugin_mentions_from_list_response。

调用图:调用 2 个内部函数(request_plugin_list, plugin_mentions_from_list_response);被 1 处调用(refresh_plugin_mentions)。

plugin_mentions_from_list_response22–36 ↗
fn plugin_mentions_from_list_response(
    response: PluginListResponse,
) -> Vec<PluginCapabilitySummary>

作用:这个函数把服务器返回的完整插件列表,压缩成一份“能被提到的插件”列表。它会跨过所有插件市场,逐个插件检查。

数据流:进去的是 PluginListResponse,里面有多个 marketplace,每个 marketplace 里有多个 plugin;它逐个取出市场名和插件信息,把每个插件交给 plugin_mention_from_summary;能转换成功的留下,不能用的插件丢掉;最后出来的是 Vec<PluginCapabilitySummary>。

调用关系:它被 fetch_plugin_mentions 调用,是请求完成后的整理步骤。它不亲自判断插件合不合格,也不亲自拼展示文字,而是把单个插件的处理交给 plugin_mention_from_summary。

调用图:被 1 处调用(fetch_plugin_mentions)。

plugin_is_eligible_for_mentions38–40 ↗
fn plugin_is_eligible_for_mentions(plugin: &PluginSummary) -> bool

作用:这个函数判断一个插件有没有资格出现在 @ 提及列表里。标准很直接:已安装、已启用、没有被管理员禁用。

数据流:进去的是一个 PluginSummary 的引用;它读取 installed、enabled、availability 三个状态;如果插件已安装且已启用,并且 availability 不是 DisabledByAdmin,就返回 true,否则返回 false。它不改动任何数据。

调用关系:它被 plugin_mention_from_summary 调用,是单个插件转换前的门卫。只有它放行的插件,后面才会继续生成 PluginCapabilitySummary。

调用图:被 1 处调用(plugin_mention_from_summary)。

plugin_mention_from_summary42–58 ↗
fn plugin_mention_from_summary(
    marketplace_name: &str,
    plugin: PluginSummary,
) -> Option<PluginCapabilitySummary>

作用:这个函数把一个插件摘要变成一个可显示、可选择的提及候选项。如果插件不合格,它直接返回空,表示不要展示。

数据流:进去的是插件所在的市场名和一个 PluginSummary;它先用 plugin_is_eligible_for_mentions 检查资格;不合格就返回 None;合格就创建 PluginCapabilitySummary,里面放入插件配置名、展示名、简介,并把技能、MCP 服务器名、应用连接器编号这些当前没有的数据设为空;最后返回 Some(...)。

调用关系:它由 plugin_mentions_from_list_response 在遍历插件时反复调用。它会把资格判断交给 plugin_is_eligible_for_mentions,把展示名选择交给 plugin_mention_display_name,把简介选择交给 plugin_mention_description,最后组装出最终候选项。

调用图:调用 3 个内部函数(plugin_is_eligible_for_mentions, plugin_mention_description, plugin_mention_display_name);外部调用 1 个(new)。

plugin_mention_display_name60–69 ↗
fn plugin_mention_display_name(plugin: &PluginSummary) -> String

作用:这个函数决定插件在 @ 列表里显示什么名字。它会优先使用插件自己提供的漂亮展示名,没有的话再用普通插件名。

数据流:进去的是一个 PluginSummary;它先查看 plugin.interface.display_name,如果存在,就去掉前后空格,并确认不是空字符串;如果可用,就返回这个展示名;否则返回 plugin.name 的副本。它只读取数据,不修改插件。

调用关系:它被 plugin_mention_from_summary 调用,专门负责“给用户看的名字”这一小块逻辑。这样主转换函数不用关心各种空值和空白字符串的细节。

调用图:被 1 处调用(plugin_mention_from_summary)。

plugin_mention_description71–83 ↗
fn plugin_mention_description(marketplace_name: &str, plugin: &PluginSummary) -> Option<String>

作用:这个函数决定插件提及候选项下面显示什么简介。它优先用插件自己的短描述;没有短描述时,用插件市场名作为兜底说明。

数据流:进去的是市场名和 PluginSummary;它先看 plugin.interface.short_description,去掉前后空格后如果不是空字符串,就返回这段描述;如果没有可用描述,就检查 marketplace_name,市场名非空时返回市场名;如果两边都没有,就返回 None。

调用关系:它被 plugin_mention_from_summary 调用,负责补充候选项的说明文字。它让最终的提及列表即使没有插件简介,也尽量有一点来源信息可看。

调用图:被 1 处调用(plugin_mention_from_summary)。

tests::plugin_mentions_use_plugin_list_summaries_and_gui_eligibility97–128 ↗
fn plugin_mentions_use_plugin_list_summaries_and_gui_eligibility()

作用:这是一个测试,用来确认插件提及列表的筛选规则和图形界面保持一致:只有可用插件会出现,禁用、未安装、被管理员禁用的插件都不会出现。

数据流:它先用 tests::plugin_summary 做出几个假插件:一个正常插件、一个被管理员禁用的插件、一个普通禁用插件、一个未安装插件;再把它们塞进假的 PluginListResponse;然后调用 plugin_mentions_from_list_response;最后用 assert_eq! 检查结果只剩正常插件,并且简介用市场名兜底。

调用关系:它是测试代码,在测试运行时执行,不参与真实 TUI 运行。它通过构造输入和期望输出,保护 plugin_mentions_from_list_response 以及后续筛选逻辑不被改坏。

调用图:外部调用 4 个(new, assert_eq!, plugin_summary, vec!)。

tests::plugin_summary130–146 ↗
fn plugin_summary(name: &str) -> PluginSummary

作用:这是测试里的小帮手,用来快速造出一个默认“正常可用”的插件摘要。测试需要不同状态的插件时,再在这个默认对象上改几个字段。

数据流:进去的是插件名字 name;它用这个名字拼出插件 id、远程插件 id 和显示名,并填好安装、启用、可用等默认状态;出来的是一个 PluginSummary。它不读取外部信息,也不产生真实插件。

调用关系:它被 tests::plugin_mentions_use_plugin_list_summaries_and_gui_eligibility 调用,用来减少测试里的重复样板代码。真正被测的生产逻辑不会调用它。

调用图:外部调用 2 个(new, format!)。

tui/src/file_search.rs源码 ↗
orchestration用户编辑 @ 文件搜索词时活跃;会话目录变化时也会被更新

当用户在聊天框里输入 @ 后面的文字时,界面会不断发来新的搜索词。这个文件里的 FileSearchManager 就像一个“搜索调度员”:它记住当前搜索目录、最新搜索词,以及正在运行的文件搜索会话。搜索词没变就不做事;搜索词清空了就关掉当前搜索;第一次有内容时才创建一个新的搜索会话。这里用互斥锁(Mutex,一把锁,防止两个任务同时改同一份数据)保护共享状态。搜索库找到结果后,会通过 TuiSessionReporter 回报。Reporter 会先检查这是不是当前这轮搜索、搜索词是不是还有效,再把结果包装成 AppEvent::FileSearchResult 发回应用。这样做很重要:用户打字很快时,旧会话可能晚一点才回结果,这个文件会把过期结果挡掉,避免界面显示错。

函数细节7
FileSearchManager::new29–39 ↗
fn new(search_dir: PathBuf, tx: AppEventSender) -> Self

作用:创建一个新的文件搜索调度员。调用方给它一个搜索目录和一个发消息回界面的通道,它就准备好监听用户的 @ 搜索输入。

数据流:输入是搜索根目录 search_dir 和应用事件发送器 tx。它新建一份共享状态:最新搜索词为空、还没有搜索会话、会话编号从 0 开始;再把目录和发送器保存起来。输出是一个可使用的 FileSearchManager,之后所有 @ 搜索都会经过它。

调用关系:它在程序启动或测试搭建界面时被调用,比如 run 和几个 make_test_app 相关函数会创建它。创建出来后,后续用户输入会交给 FileSearchManager::on_user_query,目录变化会交给 FileSearchManager::update_search_dir。

调用图:被 4 处调用(run, make_test_app, make_test_app, make_test_app_with_channels);外部调用 3 个(new, new, new)。

FileSearchManager::update_search_dir44–50 ↗
fn update_search_dir(&mut self, new_dir: PathBuf)

作用:修改文件搜索使用的目录。比如应用恢复会话后,当前工作目录变了,就要让下一次搜索去新目录里找文件。

数据流:输入是新的目录 new_dir。它先替换 manager 里保存的 search_dir,然后加锁打开共享状态,把当前搜索会话丢掉,并清空最新搜索词。结果是旧目录下的搜索不会继续用,下一次有搜索词时会基于新目录重新创建会话。

调用关系:它通常在会话恢复、工作目录变化时被上层调用。它不直接开启新搜索,而是做清理;真正重新创建搜索会话的活儿会在之后 FileSearchManager::on_user_query 收到非空搜索词时发生。

FileSearchManager::on_user_query53–73 ↗
fn on_user_query(&self, query: String)

作用:处理用户每次编辑 @ 后面搜索词的动作。它决定要不要更新搜索、关闭搜索,或者新开一个搜索会话。

数据流:输入是新的 query。它先拿锁读取当前状态:如果新词和旧词一样,就直接返回;如果不同,就保存新词。若 query 为空,它会丢掉当前搜索会话并结束。若 query 不为空但还没有会话,它会调用 FileSearchManager::start_session_locked 创建会话。最后,它把新搜索词交给搜索会话,让底层搜索库按这个词更新结果。

调用关系:它是用户打字时的主要入口。ChatComposer 发出搜索词变化后,上层会调用这里。它自己负责简单判断和状态更新,必要时把“开新搜索会话”的工作交给 FileSearchManager::start_session_locked。

调用图:调用 1 个内部函数(start_session_locked)。

FileSearchManager::start_session_locked75–99 ↗
fn start_session_locked(&self, st: &mut SearchState)

作用:在已经拿到状态锁的前提下,启动一轮新的文件搜索会话。它还会给这轮会话发一个编号,用来之后分辨结果是不是过期。

数据流:输入是可修改的 SearchState。它先把 session_token 加一,表示新一轮搜索开始;然后创建 TuiSessionReporter,让搜索库以后能把结果回报给界面;接着调用 codex-file-search 的 create_session,传入当前搜索目录和选项,其中 compute_indices 表示允许建立索引来加快搜索。成功时,它把新会话存进状态;失败时,它记录警告,并保持没有会话。

调用关系:它只由 FileSearchManager::on_user_query 调用,而且是在用户输入了非空搜索词、当前又没有搜索会话时调用。它把真正搜索文件的工作交给外部的 codex_file_search 库,把结果回报交给 TuiSessionReporter。

调用图:被 1 处调用(on_user_query);外部调用 6 个(new, default, create_session, warn!, clone, vec!)。

TuiSessionReporter::send_snapshot109–124 ↗
fn send_snapshot(&self, snapshot: &file_search::FileSearchSnapshot)

作用:把搜索库给出的某一刻搜索结果送回 TUI 界面。它会先过滤掉过期结果,避免旧搜索影响当前显示。

数据流:输入是 file_search::FileSearchSnapshot,也就是搜索库的一份结果快照,里面有查询词和匹配文件。它先加锁查看当前状态:如果这份结果来自旧会话、当前搜索词已清空,或者快照里的查询词为空,就什么都不发。确认有效后,它复制查询词和匹配结果,释放锁,再通过 app_tx 发送 AppEvent::FileSearchResult。输出不是普通返回值,而是让应用事件队列多了一条“文件搜索结果”消息。

调用关系:它由 TuiSessionReporter::on_update 调用。它处在搜索库和界面之间,像一个验票员:搜索库把结果递过来,它确认这张票属于当前这班车,才把结果放进应用的事件流。

调用图:调用 1 个内部函数(send);被 1 处调用(on_update)。

TuiSessionReporter::on_update128–130 ↗
fn on_update(&self, snapshot: &file_search::FileSearchSnapshot)

作用:这是搜索库在“有新结果”时调用的回调函数。它把底层搜索库的更新转给本文件自己的发送逻辑处理。

数据流:输入是搜索结果快照 snapshot。它不自己判断细节,而是直接调用 TuiSessionReporter::send_snapshot。结果是有效的快照会被发送成界面事件,无效或过期的快照会在 send_snapshot 里被丢弃。

调用关系:它实现了 file_search::SessionReporter 这个接口。外部 codex_file_search 会在搜索过程中调用它;它再把工作交给 TuiSessionReporter::send_snapshot,完成过滤和发送。

调用图:调用 1 个内部函数(send_snapshot)。

TuiSessionReporter::on_complete132–132 ↗
fn on_complete(&self)

作用:这是搜索库在“一轮搜索完成”时调用的回调函数,但这里故意不做任何事。因为界面只需要持续收到结果更新,不需要额外处理完成通知。

数据流:没有输入数据,也没有输出结果。调用发生时,它直接返回,不修改状态、不发送事件。

调用关系:它也是 file_search::SessionReporter 接口的一部分,所以必须提供。搜索库可能会在搜索结束时调用它;这个 TUI 文件选择忽略完成信号,只依赖 TuiSessionReporter::on_update 发送过程中的结果。

tui/src/bottom_pane/mentions_v2/mod.rs源码 ↗
othercross-cutting:编译时组织模块;运行时在 TUI 需要显示新版提及弹窗或建立搜索目录时被间接使用

在这个 TUI(终端里的文字界面)里,用户输入提及内容时,需要弹出一个统一的选择框,比如选文件、选某个候选项。这个文件本身不写具体算法,更像一个目录和门牌:它声明了这个功能由哪些子文件组成,比如候选项、过滤器、底部提示、弹窗、渲染、搜索目录和搜索模式。外部代码不用知道这些零件分别藏在哪个文件里,只要从这里拿 MentionV2PopupMentionV2Selectionbuild_search_catalog 就行。文件顶部也说明了一个重要背景:mentions_v2 功能开关暂时保留,是为了万一新版统一弹窗出问题,可以关掉它,退回旧版分开的提及弹窗和文件搜索弹窗。

tui/src/bottom_pane/mentions_v2/candidate.rs源码 ↗
data_model用户在底部输入框触发提及搜索和候选列表展示时

这个文件像一张“候选项登记表”。在底部输入框里,用户可能想提到一个文件,也可能想调用一个插件或技能。这里先用 Selection 说明选中后到底代表什么:是一个真实文件路径,还是一个工具插入文本。MentionType 给候选项分类,比如 Plugin、Skill、File、Directory,并提供一些给界面用的小能力:判断它是不是文件系统里的东西,以及生成带颜色、等宽标签的文字片段。Candidate 表示原始候选项,里面有显示名、说明、可搜索关键词、类型和选中后的动作。搜索完成后,它会变成 SearchResult,多了匹配位置和分数,方便界面高亮命中的字、按相关程度排序。整体上,这个文件不做搜索本身,而是把“候选项是什么、怎么显示、怎么变成搜索结果”这些基础约定定好。

函数细节4
MentionType::is_filesystem28–30 ↗
fn is_filesystem(self) -> bool

作用:判断这个候选项是不是来自文件系统,也就是普通文件或目录。别人可以用它来决定是否走文件路径相关的逻辑。

数据流:进去的是一个 MentionType 类型值,比如 File、Directory、Plugin 或 Skill;它只检查这个值是不是 File 或 Directory;出来的是 true 或 false,不改动任何数据。

调用关系:它是分类判断的小工具。搜索或展示流程在需要区分“文件/目录”和“插件/技能”时会调用它;内部只是用 Rust 的 matches! 宏(一种简洁的匹配写法)做判断,不再把工作交给别的项目函数。

调用图:外部调用 1 个(matches!)。

MentionType::span32–40 ↗
fn span(self, base_style: Style) -> Span<'static>

作用:把候选项类型变成界面上能显示的一段带样式文字,比如彩色的“File”或“Plugin”标签。这样列表里的不同类型一眼能分清。

数据流:进去的是候选项类型和一个基础样式;函数先根据类型选择颜色或效果,例如插件用紫色、文件用青色、技能变暗;再拿到对应文字标签,并补齐到固定宽度;出来的是一个 Span,也就是终端界面里一小段可带颜色的文字。

调用关系:它服务于候选列表的绘制阶段。界面要画每一行候选项时,会用它生成左侧类型标签;它会调用 MentionType::label 拿到文字内容,并借助 ratatui 的样式方法和 format! 宏把标签排整齐。

调用图:外部调用 4 个(cyan, dim, magenta, format!)。

MentionType::label42–49 ↗
fn label(self) -> &'static str

作用:给每种候选项类型提供一个短文字名字,比如 Plugin、Skill、File、Dir。它让显示文字集中在一个地方,不用到处手写。

数据流:进去的是一个 MentionType;函数按类型选择固定字符串;出来的是一个静态文本,不分配新对象,也不改动任何状态。

调用关系:它主要被 MentionType::span 使用,作为生成界面标签的文字来源。这样以后如果想把 Directory 显示成别的短名,只要改这里,使用标签的地方不用跟着改。

Candidate::to_result72–81 ↗
fn to_result(&self, match_indices: Option<Vec<usize>>, score: i32) -> SearchResult

作用:把一个原始候选项变成搜索结果。搜索过程算出匹配位置和分数后,会用它把这些额外信息贴到候选项上。

数据流:进去的是一个 Candidate、可选的匹配字符位置列表,以及一个分数;函数复制候选项里的显示名、说明、类型和选中信息,再加上匹配位置和分数;出来的是 SearchResult,原来的 Candidate 不会被改动。

调用关系:它位于“候选项准备好”到“搜索结果可展示/可排序”之间。搜索代码找到匹配后会调用它生成 SearchResult;内部使用 clone 复制字符串、选中动作等数据,避免结果对象依赖原始候选项的生命周期。

调用图:外部调用 1 个(clone)。

tui/src/bottom_pane/mentions_v2/search_mode.rs源码 ↗
domain_logic用户在提及搜索界面切换筛选模式、刷新候选列表时

在底部输入框里搜索“提及”内容时,候选结果可能来自不同地方:普通结果、文件和目录、插件和技能。这个文件就像一个小小的筛选旋钮,规定旋钮有哪几档,以及往前拨、往后拨会到哪一档。SearchMode 是一个枚举,也就是“只能从几个固定选项里选一个”的类型。它还带了几个简单规则:previous 和 next 负责循环切换模式;accepts 负责判断某个候选项能不能在当前模式下出现;label 负责给当前模式一个给人看的名字。重要的是,FilesystemOnly 不是什么都收,只收文件和目录;Tools 只收插件和技能;Results 则全部放行。

函数细节4
SearchMode::previous11–17 ↗
fn previous(self) -> Self

作用:把当前搜索模式切到“上一个”模式。因为模式是循环的,所以如果现在是第一个模式,再往前会绕到最后一个。

数据流:进去的是当前的 SearchMode,比如“全部结果”或“只看文件系统”。它按固定顺序往前退一档:全部结果退到插件,文件系统退到全部结果,插件退到文件系统。出来的是新的 SearchMode,不改动别的东西。

调用关系:当外层的 previous_search_mode 想响应用户“切到上一个筛选”的操作时,会调用它。它自己不调用别的函数,只负责给出切换后的模式。

调用图:被 1 处调用(previous_search_mode)。

SearchMode::next19–25 ↗
fn next(self) -> Self

作用:把当前搜索模式切到“下一个”模式。它让用户可以像按遥控器换频道一样,在几个筛选范围之间循环切换。

数据流:进去的是当前的 SearchMode。它按固定顺序往后走一档:全部结果到只看文件系统,只看文件系统到插件,插件再回到全部结果。出来的是新的 SearchMode,不产生额外副作用。

调用关系:当外层的 next_search_mode 想响应用户“切到下一个筛选”的操作时,会调用它。它是切换流程里的小齿轮,只算出下一档,不负责更新界面本身。

调用图:被 1 处调用(next_search_mode)。

SearchMode::accepts27–35 ↗
fn accepts(self, mention_type: MentionType) -> bool

作用:判断某个候选项在当前搜索模式下该不该显示。简单说,它就是列表刷新前的“门卫”。

数据流:进去的是当前 SearchMode 和一个 MentionType,也就是候选项的类型,比如文件、目录、插件或技能。它根据模式检查类型:全部结果模式直接放行;只看文件系统时只放行文件和目录;插件模式只放行插件和技能。出来的是 true 或 false,表示这个候选项能不能留下。

调用关系:filtered_candidates 在整理要显示的候选列表时会调用它,用它来筛掉不符合当前模式的项目。函数内部用 matches! 这种 Rust 的匹配写法来判断类型是否属于指定几类。

调用图:被 1 处调用(filtered_candidates);外部调用 1 个(matches!)。

SearchMode::label37–43 ↗
fn label(self) -> &'static str

作用:给当前搜索模式取一个适合显示在界面上的文字标签。这样程序内部的模式名字不用直接暴露给用户看。

数据流:进去的是当前 SearchMode。它把内部选项翻译成固定英文文案:Results 变成“All Results”,FilesystemOnly 变成“Filesystem Only”,Tools 变成“Plugins”。出来的是一段静态字符串,也就是程序里固定保存、不需要临时创建的文字。

调用关系:它位于界面展示这一侧:当别的代码需要显示当前筛选模式名称时可以调用它。调用图里没有显示它再调用其他函数,它只是把模式转换成可读标签。

tui/src/bottom_pane/mentions_v2/search_catalog.rs源码 ↗
domain_logic用户打开或刷新 mentions 搜索列表时

这个文件做的事很像给通讯录建索引:系统里有很多技能和插件,它把它们统一包装成 Candidate(候选项,也就是搜索结果里的每一行)。每个候选项都有显示名称、说明文字、可被搜索到的关键词,以及用户选中后要插入到输入框里的文本。技能会插入类似 $技能名 的文字,并带上技能文件路径;插件会插入类似 @插件名 的文字,并带上 plugin://... 这样的插件地址。文件里还特别处理了插件名字的大小写和分隔符,比如把 mcp-search 和显示名 MCP Search 组合成更好看的 MCP-Search。如果插件没有写说明,它会根据插件能力自动补一句,比如有几个 MCP server(外部工具服务)或几个 app(应用连接)。

函数细节12
build_search_catalog11–25 ↗
fn build_search_catalog(
    skills: Option<&[SkillMetadata]>,
    plugins: Option<&[PluginCapabilitySummary]>,
) -> Vec<Candidate>

作用:把传进来的技能列表和插件列表合并成一份统一的搜索候选清单。界面要显示“可提及的东西”时,会需要这份清单。

数据流:进去的是可选的技能数组和可选的插件数组;它先准备一个空列表,然后把每个技能变成技能候选项,把每个插件变成插件候选项;出来的是一个 Candidate 列表,里面混放了技能和插件,供搜索框使用。

调用关系:这是本文件对外的入口函数。它负责把两类来源汇总起来,具体怎么把单个技能或单个插件变成候选项,则分别交给 skill_candidate 和 plugin_candidate。

调用图:外部调用 1 个(new)。

skill_candidate27–46 ↗
fn skill_candidate(skill: &SkillMetadata) -> Candidate

作用:把一个 SkillMetadata(技能的元数据,也就是技能的名字、路径等资料)转换成搜索结果里的一项。用户选中这一项后,输入框会插入 $技能名

数据流:进去的是一个技能资料;它取出适合展示的名字,整理说明文字,准备搜索关键词,并记录技能文件路径;出来的是一个 Candidate,类型标记为 Skill,选中后会插入 $skill_name,同时保留技能说明文件的路径。

调用关系:build_search_catalog 在整理技能列表时会用它。它会调用 skill_display_name 取得好看的名字,也会调用 optional_skill_description 清理技能说明。

调用图:调用 2 个内部函数(optional_skill_description, skill_display_name);外部调用 2 个(format!, vec!)。

plugin_candidate48–72 ↗
fn plugin_candidate(plugin: &PluginCapabilitySummary) -> Candidate

作用:把一个插件能力摘要转换成搜索结果里的一项。用户选中这一项后,输入框会插入 @插件名

数据流:进去的是插件摘要,里面有配置名、显示名、说明和能力信息;它拆出插件本名和市场名,生成适合提及的名字,收集可搜索关键词,再补上说明;出来的是一个 Candidate,类型标记为 Plugin,选中后插入 @...,并带上 plugin://配置名 这样的地址。

调用关系:build_search_catalog 在整理插件列表时会用它。它把取提及名的细活交给 plugin_mention_name,把说明文字的兜底生成交给 plugin_description。

调用图:调用 2 个内部函数(plugin_description, plugin_mention_name);外部调用 2 个(format!, vec!)。

plugin_mention_name74–96 ↗
fn plugin_mention_name(plugin_name: &str, display_name: &str) -> String

作用:生成用户实际要输入的插件提及名,也就是 @ 后面的那段文字。它尽量让名字既能对应真实插件名,又看起来符合插件显示名的大小写。

数据流:进去的是插件内部名字和界面显示名;它分别把两边切成一段一段来比较,如果段数和内容能对上,就沿用显示名的大小写并保留原插件名里的 -_;如果对不上,就退回到把插件名做成首字母大写的形式;出来的是最终用于插入输入框的名字。

调用关系:plugin_candidate 需要生成 @插件名 时会调用它。它会先请 split_plugin_name_segments 和 split_display_name_segments 拆名字,必要时再请 title_case_plugin_name 做兜底格式化。

调用图:调用 3 个内部函数(split_display_name_segments, split_plugin_name_segments, title_case_plugin_name);被 1 处调用(plugin_candidate);外部调用 1 个(new)。

split_plugin_name_segments98–117 ↗
fn split_plugin_name_segments(plugin_name: &str) -> Vec<(String, Option<char>)>

作用:把插件内部名字按 -_ 拆成几段,同时记住每段后面原来跟着哪个分隔符。这样后面重组名字时不会丢掉原本的连接符。

数据流:进去的是类似 google_calendarmcp-search 的插件名;它逐个字符看,遇到 -_ 就结束当前一段并记下分隔符;出来的是一组“文字段 + 可选分隔符”的数据。

调用关系:plugin_mention_name 用它来理解插件内部名字的结构。它的结果会和显示名拆出来的段一起比较,用来判断能不能安全地采用显示名的大小写。

调用图:被 1 处调用(plugin_mention_name);外部调用 4 个(new, new, matches!, take)。

split_display_name_segments119–125 ↗
fn split_display_name_segments(display_name: &str) -> Vec<String>

作用:把插件显示名按非字母数字的字符拆成几段。比如 MCP Search 会拆成 MCPSearch

数据流:进去的是人眼看到的插件显示名;它用空格、标点等非英文数字字符当分隔线,丢掉空段;出来的是一组干净的文字段。

调用关系:plugin_mention_name 用它来和插件内部名字的各段做对比。如果两边段数和内容能对上,就说明显示名可以用来提供更好看的大小写。

调用图:被 1 处调用(plugin_mention_name)。

title_case_plugin_name127–148 ↗
fn title_case_plugin_name(plugin_name: &str) -> String

作用:在显示名不能可靠套用时,给插件名做一个简单美化:每段开头的英文字母变成大写,分隔符保留。

数据流:进去的是插件内部名字;它从左到右看字符,开头或 -_ 后面的英文字母会转成大写,其他字符基本保持不变;出来的是更适合展示和插入的名字。

调用关系:plugin_mention_name 在发现显示名和插件名对不上时会调用它,作为安全的兜底方案。

调用图:被 1 处调用(plugin_mention_name);外部调用 2 个(with_capacity, matches!)。

plugin_description150–159 ↗
fn plugin_description(plugin: &PluginCapabilitySummary) -> Option<String>

作用:给插件候选项准备说明文字。插件自己写了说明就用自己的;没写时,就根据它有哪些能力自动补一句。

数据流:进去的是插件摘要;它先计算插件能力标签,比如有 skills、有几个 MCP server、有几个 app;如果插件已有 description,就直接返回它;如果没有,就返回 PluginPlugin - ... 这样的说明;出来的是可放到搜索结果里的说明文字。

调用关系:plugin_candidate 在创建插件候选项时会调用它。它把统计能力标签的工作交给 plugin_capability_labels。

调用图:调用 1 个内部函数(plugin_capability_labels);被 1 处调用(plugin_candidate)。

plugin_capability_labels161–183 ↗
fn plugin_capability_labels(plugin: &PluginCapabilitySummary) -> Vec<String>

作用:把插件的能力变成几段短标签,方便显示在说明里。比如“有技能”“有 2 个 MCP server”“有 1 个 app”。

数据流:进去的是插件摘要;它检查插件是否带技能、MCP server 名单是否为空、app 连接器名单是否为空,并按数量生成单数或复数文字;出来的是一个字符串列表,供说明文字拼接使用。

调用关系:plugin_description 用它来生成插件没有自带说明时的兜底描述。它不直接面对界面,只提供可读标签。

调用图:被 1 处调用(plugin_description);外部调用 2 个(new, format!)。

optional_skill_description185–188 ↗
fn optional_skill_description(skill: &SkillMetadata) -> Option<String>

作用:取得技能说明,并把空白说明变成“没有说明”。这样界面不会显示一段空字符串。

数据流:进去的是技能资料;它调用 skill_description 取出说明文字,再去掉前后空格;如果剩下内容不为空,就返回这段说明,否则返回空值;出来的是可选的说明文字。

调用关系:skill_candidate 在创建技能候选项时会调用它。它负责把底层技能说明整理成界面可以直接使用的形式。

调用图:调用 1 个内部函数(skill_description);被 1 处调用(skill_candidate)。

tests::plugin_mention_name_uses_display_segments_when_they_match_plugin_name196–205 ↗
fn plugin_mention_name_uses_display_segments_when_they_match_plugin_name()

作用:这是一个测试,确认当插件内部名字和显示名其实是同几个词时,函数会采用显示名里的大小写,同时保留原分隔符。

数据流:进去的是几组固定例子,比如 mcp-searchMCP Search;测试调用 plugin_mention_name,看输出是不是 MCP-Search 等预期结果;如果不一样,测试就失败。

调用关系:它在测试运行时执行,用来保护 plugin_mention_name 的主要规则,避免以后改代码时不小心把好看的大小写处理弄坏。

调用图:外部调用 1 个(assert_eq!)。

tests::plugin_mention_name_falls_back_to_title_cased_plugin_name208–214 ↗
fn plugin_mention_name_falls_back_to_title_cased_plugin_name()

作用:这是一个测试,确认当显示名不能直接对应插件内部名字时,函数会退回到安全的首字母大写规则。

数据流:进去的是几组固定例子,比如 sampleSample Plugin;测试调用 plugin_mention_name,检查输出是否是 Sample 等预期结果;结果不符时测试失败。

调用关系:它在测试运行时执行,专门覆盖 plugin_mention_name 的兜底路线,也间接保证 title_case_plugin_name 的效果符合预期。

调用图:外部调用 1 个(assert_eq!)。

tui/src/bottom_pane/mentions_v2/filter.rs源码 ↗
domain_logic用户在提及弹窗里输入或更新搜索词时活跃

这个文件像一个小型“候选项整理员”。当用户在界面底部输入提及内容时,系统手里可能有两类东西:一类是早就准备好的候选项,比如插件、技能;另一类是文件搜索返回的文件或目录。它先看当前搜索模式允许显示哪些类型,不合适的直接跳过。然后如果用户没输入过滤词,就把候选项原样放进去;如果输入了字,就用模糊匹配(不要求连续输入完整名字,也能猜出想找谁)来判断是否匹配,并记录匹配分数和高亮位置。文件搜索结果会被转换成统一的搜索结果格式。最后,它把所有结果排序:插件和技能排在文件前面;同一类里再按分数、是否直接命中显示名、名字字母顺序等规则排。这样用户看到的是一份既相关又稳定的列表,而不是杂乱无章的一堆结果。

函数细节5
filtered_candidates11–46 ↗
fn filtered_candidates(
    candidates: &[Candidate],
    file_matches: &[FileMatch],
    query: &str,
    search_mode: SearchMode,
    show_file_matches: bool,
) -> Vec<SearchResult>

作用:这是本文件的主入口,用来把“所有可能的候选项”和“文件搜索结果”整理成最终要显示的一行行结果。它会按用户输入、搜索模式和是否显示文件结果来决定留下谁、丢掉谁、怎么排序。

数据流:进去的是候选项列表、文件匹配列表、用户输入的查询文字、当前搜索模式,以及一个是否显示文件匹配的开关。它先把查询文字去掉前后空格,再逐个检查候选项:类型不被当前模式接受就跳过;没有查询词就直接加入;有查询词就交给 best_tool_match 看是否匹配。若允许显示文件结果,它还会把 FileMatch 转成 SearchResult。最后交给 sort_rows 排好顺序,出来的是一份可以直接给界面显示的 SearchResult 列表。

调用关系:它由 rows 调用,处在“界面需要刷新候选列表”的位置。它自己不做所有细活,而是把匹配判断交给 best_tool_match,把文件结果转换交给 file_match_to_row,把最后排序交给 sort_rows;同时它会询问搜索模式的 accepts,确认某类结果现在能不能显示。

调用图:调用 3 个内部函数(best_tool_match, sort_rows, accepts);被 1 处调用(rows);外部调用 2 个(new, iter)。

best_tool_match48–60 ↗
fn best_tool_match(candidate: &Candidate, filter: &str) -> Option<(Option<Vec<usize>>, i32)>

作用:这个函数判断一个普通候选项是否能匹配用户输入,并找出最合适的匹配分数。它优先匹配显示给用户看的名字;如果名字不匹配,再试它的其他搜索关键词。

数据流:进去的是一个候选项和用户输入的过滤词。它先拿候选项的 display_name 做 fuzzy_match,也就是模糊匹配;如果命中,就返回可高亮的位置和分数。如果显示名没命中,它再检查 search_terms 里的其他词,但会跳过和显示名重复的词;这些词命中时只返回分数,不返回高亮位置。最后出来的是“匹配位置可有可无,加上分数”的结果;如果完全不匹配,就出来空值。

调用关系:它被 filtered_candidates 调用,是候选项能不能进入结果列表的判断员。它把真正的模糊匹配工作交给外部的 fuzzy_match,自己负责决定先匹配哪个字段、匹配成功后怎样包装结果。

调用图:被 1 处调用(filtered_candidates);外部调用 1 个(fuzzy_match)。

sort_rows62–75 ↗
fn sort_rows(rows: &mut [SearchResult], filter: &str)

作用:这个函数负责把已经筛出来的结果排成用户更容易看的顺序。它先按大类排,再在同一类里按相关度和名字排。

数据流:进去的是一组可修改的搜索结果,以及当前过滤词。它给不同类型定了一个固定优先级:插件最靠前,技能其次,文件和目录再后。然后对列表原地排序,也就是直接改动传进来的数组顺序。排序时同类型的细节比较会交给 compare_within_rank,最后如果还分不出先后,就按显示名排序。它不返回新列表,而是把原列表整理好。

调用关系:它由 filtered_candidates 在所有结果收集完成后调用,属于最后的收尾步骤。它使用标准排序能力 sort_by,并在排序规则里借助 compare_within_rank 判断同一优先级里的先后。

调用图:被 1 处调用(filtered_candidates);外部调用 1 个(sort_by)。

compare_within_rank77–89 ↗
fn compare_within_rank(a: &SearchResult, b: &SearchResult, filter: &str) -> std::cmp::Ordering

作用:这个函数专门比较两个同类结果谁更应该排前面。它处理一些细规则,比如文件结果更看重搜索分数,非文件结果更看重是不是直接命中显示名。

数据流:进去的是两个搜索结果和当前过滤词。它先看两者是不是文件系统类结果,也就是文件或目录;如果是,就让分数高的排前面。不是文件类时,如果用户没有输入过滤词,就按显示名排序。用户输入了过滤词时,它优先让有高亮匹配位置的结果靠前,再按分数比较。出来的是一个排序判断:前者在前、后者在前,或者两者暂时相同。

调用关系:它服务于 sort_rows,是排序规则里的“细分裁判”。sort_rows 先决定大类顺序,然后在同一大类里调用它来判断两个结果的前后。

file_match_to_row91–107 ↗
fn file_match_to_row(file_match: &FileMatch) -> SearchResult

作用:这个函数把文件搜索系统返回的结果,转换成提及弹窗能显示的统一格式。没有它,文件搜索结果和普通候选项就不是同一种形状,后面不好一起排序和展示。

数据流:进去的是一个 FileMatch,里面有路径、匹配类型、匹配位置和分数。它先把文件搜索里的类型翻译成本界面使用的 MentionType:文件变成 File,目录变成 Directory。然后把路径转成显示文字,把路径本身放进 Selection::File 作为用户选中后的目标,把匹配位置转成 usize 列表,把分数转成整数。出来的是一个 SearchResult,可以和插件、技能等候选项放在同一个列表里。

调用关系:它在 filtered_candidates 处理文件搜索结果时被使用。filtered_candidates 负责决定要不要加入文件结果;一旦要加入,就靠它把外部文件搜索的 FileMatch 包装成界面内部统一认识的 SearchResult。

调用图:外部调用 1 个(File)。

tui/src/bottom_pane/mentions_v2/popup.rs源码 ↗
domain_logic用户输入提及内容、搜索结果返回、弹窗绘制和键盘上下移动时活跃

可以把这个文件想成一个小型点菜单。Popup 保存用户正在搜的文字、候选列表、文件搜索结果、当前搜索模式,以及滚动和选中状态。用户改了输入内容,它会更新查询,并让 FileSearch 记住“现在等的是哪次搜索结果”。等文件搜索结果回来时,它只接受和当前查询匹配的结果,避免慢一步回来的旧结果污染界面。每次候选项、查询、搜索模式或文件结果变化后,它都会重新“夹住”选中位置,确保光标不会指到不存在的行。真正要显示的行由 rows 统一算出来,再交给 render_popup 画出来。FileSearch 是里面的一个小帮手,专门处理文件搜索的等待状态、结果列表,以及空列表时该显示“loading...”还是“no matches”。

函数细节17
Popup::new24–32 ↗
fn new(candidates: Vec<Candidate>) -> Self

作用:创建一个新的搜索弹窗,并放入最初的候选项。它会把查询清空、文件搜索状态归零、搜索模式设为默认结果模式,并准备好滚动状态。

数据流:进去的是一批候选项 → 函数把它们存进 Popup,同时新建空查询、默认的 FileSearch、默认搜索模式和新的滚动状态 → 出来的是一个可以立刻使用的 Popup。

调用关系:这是弹窗生命周期的起点。外部在需要显示提及候选菜单时调用它,之后用户输入、搜索结果和键盘操作都会继续修改这个 Popup。

调用图:调用 1 个内部函数(new);外部调用 2 个(new, default)。

Popup::set_candidates34–37 ↗
fn set_candidates(&mut self, candidates: Vec<Candidate>)

作用:替换弹窗里的普通候选项列表。比如项目状态变了,可选的人、命令或条目也要跟着更新。

数据流:进去的是新的候选项列表 → 函数覆盖旧列表,然后重新检查当前选中位置是否合法 → 没有返回值,但 Popup 内部的候选项和选中/滚动状态可能被调整。

调用关系:外部拿到新候选项时会调用它。它不会自己计算显示行,而是把“别让选中项跑出范围”的工作交给 Popup::clamp_selection。

调用图:调用 1 个内部函数(clamp_selection)。

Popup::set_query39–43 ↗
fn set_query(&mut self, query: &str)

作用:更新用户正在输入的搜索文字。它同时通知文件搜索小帮手:现在应该等哪一个查询的文件结果。

数据流:进去的是一段查询字符串 → 函数把它保存到 Popup,也交给 FileSearch::set_query 处理等待状态,然后重新校正选中位置 → 出来没有直接结果,但弹窗的过滤条件和文件搜索状态变了。

调用关系:用户每次改输入时通常会走这里。它先让 FileSearch 记住新查询,再调用 Popup::clamp_selection,保证下一次显示和移动时状态安全。

调用图:调用 2 个内部函数(set_query, clamp_selection)。

Popup::set_file_matches45–48 ↗
fn set_file_matches(&mut self, query: &str, matches: Vec<FileMatch>)

作用:接收异步文件搜索返回的匹配结果,并放进弹窗里。它会防止旧查询的搜索结果覆盖新查询的界面。

数据流:进去的是这批结果对应的查询文字和文件匹配列表 → 函数交给 FileSearch::set_matches 检查是不是当前还在等的查询,再保存最多可显示的结果,然后校正选中位置 → 没有返回值,但文件匹配区和弹窗选中状态可能更新。

调用关系:文件搜索后台完成后会调用它。它依赖 FileSearch::set_matches 过滤掉过期结果,然后用 Popup::clamp_selection 让界面状态继续稳定。

调用图:调用 2 个内部函数(set_matches, clamp_selection)。

Popup::selected50–54 ↗
fn selected(&self) -> Option<Selection>

作用:取出当前用户选中的那一项。比如用户按回车时,外部需要知道应该插入或打开哪个结果。

数据流:进去的是当前 Popup 状态 → 函数先用 Popup::rows 算出当前真正显示的行,再用滚动状态里的选中编号去取对应 Selection → 出来是一个可能存在的选择结果;如果没有选中或编号无效,就返回空。

调用关系:它通常在确认选择时被外部使用。它不自己过滤数据,而是依赖 Popup::rows 得到和屏幕一致的列表,避免“看见的是 A,选中却是 B”。

调用图:调用 1 个内部函数(rows)。

Popup::move_up56–60 ↗
fn move_up(&mut self)

作用:让弹窗里的选中项向上移动一格。到顶部时会按滚动状态的规则循环或处理边界,并保证选中项仍然看得见。

数据流:进去的是当前 Popup 状态和用户的“上移”动作 → 函数先算出当前行数,再让 ScrollState 上移选中位置,最后调整滚动窗口让选中行保持可见 → 没有返回值,但选中位置和滚动位置会改变。

调用关系:用户按上方向键时会用到它。它通过 Popup::rows 知道当前有多少行,再把具体移动和可见性处理交给 ScrollState 的 move_up_wrap 和 ensure_visible。

调用图:调用 3 个内部函数(rows, ensure_visible, move_up_wrap)。

Popup::move_down62–66 ↗
fn move_down(&mut self)

作用:让弹窗里的选中项向下移动一格。它和 move_up 对称,用来响应下方向键。

数据流:进去的是当前 Popup 状态和用户的“下移”动作 → 函数算出当前显示行数,让 ScrollState 下移选中位置,再把滚动区域调到能看见选中项 → 没有返回值,但选中位置和滚动位置会改变。

调用关系:用户按下方向键时会走这里。它依赖 Popup::rows 得到列表长度,再交给 ScrollState 的 move_down_wrap 和 ensure_visible 做具体移动。

调用图:调用 3 个内部函数(rows, ensure_visible, move_down_wrap)。

Popup::previous_search_mode68–71 ↗
fn previous_search_mode(&mut self)

作用:切到上一个搜索模式。搜索模式可以理解成“用哪种方式看结果”,比如普通候选和文件匹配之间的不同展示策略。

数据流:进去的是当前搜索模式 → 函数调用 SearchMode 的 previous 得到上一个模式,保存起来,然后重新校正选中和滚动状态 → 没有返回值,但弹窗模式和选中状态会变。

调用关系:用户切换搜索范围或模式时会用到它。模式变化会改变 rows 算出来的行,所以它必须随后调用 Popup::clamp_selection 防止选中位置失效。

调用图:调用 2 个内部函数(clamp_selection, previous)。

Popup::next_search_mode73–76 ↗
fn next_search_mode(&mut self)

作用:切到下一个搜索模式。它让用户可以在不同类型的搜索结果展示之间向前切换。

数据流:进去的是当前搜索模式 → 函数调用 SearchMode 的 next 得到下一个模式,保存起来,再重新检查选中项是否还合法 → 没有返回值,但弹窗显示逻辑可能改变。

调用关系:这是 previous_search_mode 的反方向操作。外部在用户按切换快捷键时调用它,它再用 Popup::clamp_selection 收尾,保证新模式下不会选到不存在的行。

调用图:调用 2 个内部函数(clamp_selection, next)。

Popup::calculate_required_height78–80 ↗
fn calculate_required_height(&self, _width: u16) -> u16

作用:告诉布局系统这个弹窗大概要占多高。这里固定按最大行数再加上边框或装饰空间来算。

数据流:进去的是可用宽度,但这个函数目前不使用宽度 → 它用 MAX_POPUP_ROWS 加 2 算出高度,并用安全加法避免数字溢出 → 出来的是弹窗需要的高度。

调用关系:界面排版时会问它要高度。它不参与搜索和选择,只给外层布局一个稳定的尺寸建议。

Popup::clamp_selection82–86 ↗
fn clamp_selection(&mut self)

作用:把当前选中位置“夹”回合法范围内。就像菜单变短后,原来选第 10 项,但现在只有 3 项,它会把选择修正到不会出错的位置。

数据流:进去的是当前 Popup 状态 → 函数用 Popup::rows 算出现在有多少行,再让 ScrollState 修正选中编号,并调整滚动窗口让选中行可见 → 没有返回值,但选中和滚动状态可能被修正。

调用关系:很多会改变列表内容的操作都会调用它,包括 set_candidates、set_query、set_file_matches、previous_search_mode 和 next_search_mode。它是弹窗状态安全的兜底步骤。

调用图:调用 3 个内部函数(rows, clamp_selection, ensure_visible);被 5 处调用(next_search_mode, previous_search_mode, set_candidates, set_file_matches, set_query)。

Popup::rows88–96 ↗
fn rows(&self) -> Vec<SearchResult>

作用:算出弹窗当前真正应该显示的每一行。它把普通候选、文件匹配、用户查询和搜索模式合在一起,变成统一的搜索结果列表。

数据流:进去的是 Popup 保存的候选项、文件搜索结果、查询文字、搜索模式,以及 FileSearch 是否有结果可展示的信息 → 函数调用 filtered_candidates 做过滤和组合 → 出来是一组 SearchResult,也就是渲染、选择和移动都使用的行列表。

调用关系:这是弹窗里很多动作的共同基础。selected、move_up、move_down、clamp_selection 和 render_ref 都靠它拿到和当前状态一致的显示行。

调用图:调用 2 个内部函数(filtered_candidates, should_show_matches);被 5 处调用(clamp_selection, move_down, move_up, render_ref, selected)。

Popup::render_ref100–109 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

作用:把弹窗画到终端界面上。它实现的是 WidgetRef,也就是 ratatui 这个终端界面库要求的“会画自己的组件”接口。

数据流:进去的是要绘制的屏幕区域和缓冲区,加上 Popup 当前状态 → 函数算出当前显示行,取出空结果提示文字和搜索模式,然后交给 render_popup 写进缓冲区 → 出来没有普通返回值,但终端绘制缓冲区被填上弹窗内容。

调用关系:界面每次刷新时会调用它。它自己不画细节,而是把准备好的数据交给 render_popup;空状态文字来自 FileSearch::empty_message,列表来自 Popup::rows。

调用图:调用 3 个内部函数(empty_message, rows, render_popup)。

FileSearch::set_query121–131 ↗
fn set_query(&mut self, query: &str)

作用:记录当前文件搜索应该对应哪段查询,并维护“是否正在等待结果”的状态。它的关键作用是让界面知道该显示加载中,还是清空旧结果。

数据流:进去的是新的查询字符串 → 如果查询为空,它清掉等待查询、显示查询、等待标记和旧匹配;如果查询变了,它把新查询记为 pending_query,并标记正在等待 → 没有返回值,但 FileSearch 的内部状态会更新。

调用关系:Popup::set_query 会调用它。它是文件搜索状态的入口,后面的 FileSearch::set_matches 会用 pending_query 判断返回结果是不是还有效。

调用图:被 1 处调用(set_query)。

FileSearch::set_matches133–141 ↗
fn set_matches(&mut self, query: &str, matches: Vec<FileMatch>)

作用:保存文件搜索返回的结果,但只保存当前还在等待的那次查询的结果。这样可以避免网络或后台搜索慢半拍时,把过期结果显示出来。

数据流:进去的是结果对应的查询和一批文件匹配 → 如果查询不是当前 pending_query,它直接忽略;如果匹配,就把显示查询更新为这次查询,保存最多 MAX_POPUP_ROWS 条结果,并把等待状态关掉 → 没有返回值,但有效文件结果会进入 FileSearch。

调用关系:Popup::set_file_matches 会在后台搜索完成后调用它。它和 FileSearch::set_query 配合,像给每次搜索贴标签,只有标签对得上的结果才会被弹窗使用。

调用图:被 1 处调用(set_file_matches)。

FileSearch::should_show_matches143–145 ↗
fn should_show_matches(&self) -> bool

作用:告诉外层现在是否应该把文件匹配结果展示出来。规则很简单:只要已有匹配结果,就可以展示。

数据流:进去的是 FileSearch 当前保存的 matches → 函数检查列表是否为空 → 出来是 true 或 false,表示是否有文件匹配可显示。

调用关系:Popup::rows 会调用它,并把这个判断交给 filtered_candidates。它影响最终显示行里是否包含文件搜索结果。

调用图:被 1 处调用(rows)。

FileSearch::empty_message147–153 ↗
fn empty_message(&self) -> &'static str

作用:决定弹窗没有可显示文件结果时该提示什么。正在等结果就显示“loading...”,等完但没结果就显示“no matches”。

数据流:进去的是 FileSearch 的 waiting 状态 → 函数根据 waiting 选择一段固定提示文字 → 出来是用于界面显示的字符串。

调用关系:Popup::render_ref 会调用它,再把提示文字传给 render_popup。这样绘制层不用知道搜索是不是还在路上,只负责把提示画出来。

调用图:被 1 处调用(render_ref)。

可复用选择和设置弹窗

这些文件提供通用列表和多选选择控件,并将其应用到整个底部窗格中的具体设置和开关弹窗。

tui/src/bottom_pane/list_selection_view.rs源码 ↗
domain_logicmain loop / key handling / rendering

可以把这个文件想成一个“终端里的点餐菜单”。外部先用 SelectionViewParams 把菜单标题、选项、搜索框、页脚提示、右侧预览等材料交进来,ListSelectionView 就负责把它画到屏幕上,并处理用户按键。它会维护当前选中哪一行、滚动到哪里、搜索后还剩哪些行、哪些行不能选。搜索时,它把可见行和真实数据行对应起来,避免用户看到第 2 行却选错原始项目。它还支持标签页,像菜单分组;支持右侧预览,屏幕宽就放旁边,窄就放下面;支持取消回调,用来恢复临时预览。底部的测试覆盖了各种容易出错的细节,比如窄屏不丢行、禁用项不能被选、搜索粘贴要清理、右侧预览背景不能被误擦掉。

函数细节107
SideContentWidth::default68–70 ↗
fn default() -> Self

作用:给右侧内容宽度一个默认值:默认不显示右侧面板。这样普通选择框不会无意间占用额外空间。

数据流:没有输入 → 生成 SideContentWidth::Fixed(0),也就是固定宽度为 0 → 返回这个默认设置。

调用关系:SelectionViewParams::default 会用到它,让新建选择框参数时自动处于“没有右侧预览”的安全状态。

调用图:被 1 处调用(default);外部调用 1 个(Fixed)。

popup_content_width75–77 ↗
fn popup_content_width(total_width: u16) -> u16

作用:算出弹窗真正能放内容的宽度。弹窗左右各有内边距,这个函数把那部分扣掉。

数据流:输入弹窗总宽度 → 减去固定的左右留白,如果太窄就安全地变成 0 → 输出内容区宽度。

调用关系:desired_height 和 render 都会先问它内容区有多宽,再决定文字怎么换行、右侧预览能不能放下。

调用图:被 2 处调用(desired_height, render)。

side_by_side_layout_widths82–97 ↗
fn side_by_side_layout_widths(
    content_width: u16,
    side_content_width: SideContentWidth,
    side_content_min_width: u16,
) -> Option<(u16, u16)>

作用:判断列表和右侧预览能不能并排放。如果空间不够,就告诉调用者别并排,改用上下堆叠。

数据流:输入内容总宽、右侧宽度模式、右侧最小宽度 → 算右侧宽度,再检查右侧是否太窄、列表是否会被挤得不能用 → 输出列表宽和右侧宽,或者 None。

调用关系:ListSelectionView::side_layout_width 把具体配置交给它;渲染和高度计算再根据结果选择“左右布局”或“上下布局”。

调用图:被 1 处调用(side_layout_width)。

SelectionViewParams::default211–238 ↗
fn default() -> Self

作用:提供一套安全的默认弹窗配置。调用者只需要填自己关心的字段,其他地方不会缺值。

数据流:没有输入 → 填好空标题、空列表、无搜索、默认列宽、空的 header 和 side_content、允许取消等默认值 → 返回 SelectionViewParams。

调用关系:创建各种选择弹窗和测试时大量使用它,再把覆盖后的参数交给 ListSelectionView::new。

调用图:调用 1 个内部函数(default);外部调用 2 个(new, new)。

ListSelectionView::new292–360 ↗
fn new(
        params: SelectionViewParams,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

作用:真正创建一个可交互的列表选择弹窗。它把外部给的配置变成运行时状态,比如当前标签页、筛选结果、选中行。

数据流:输入 SelectionViewParams、事件发送器和按键映射 → 组合标题和副标题,选择初始标签页,初始化滚动状态,应用搜索过滤,必要时选中第一条可用项 → 输出 ListSelectionView。

调用关系:上层弹窗系统在要显示选择框时调用它;它会立刻调用 apply_filter,让后续 render 和 handle_key_event 面对的是一致状态。

调用图:调用 2 个内部函数(new, with);被 23 处调用(replace_active_views_with_selection_view, replace_selection_view_if_active, replace_selection_view_if_present, show_selection_view, new, set_current, auto_all_rows_col_width_does_not_shift_when_scrolling, c0_ctrl_n_respects_unbound_list_move_down, c0_ctrl_p_respects_remapped_list_move_down, c0_ctrl_p_respects_unbound_list_move_up (+13 more));外部调用 3 个(new, new, new)。

ListSelectionView::visible_len362–364 ↗
fn visible_len(&self) -> usize

作用:返回当前筛选后还能看到多少行。导航和滚动都需要这个数字。

数据流:读取 filtered_indices → 计算长度 → 返回可见行数量。

调用关系:move_up、move_down、page_up、page_down、jump_top、jump_bottom 和跳过禁用项的函数都会用它防止越界。

调用图:被 10 处调用(jump_bottom, jump_top, move_down, move_up, page_down, page_up, skip_disabled_down, skip_disabled_down_clamped, skip_disabled_up, skip_disabled_up_clamped)。

ListSelectionView::tabs_enabled366–368 ↗
fn tabs_enabled(&self) -> bool

作用:判断这个选择框是否启用了标签页。没有标签页时,左右切换按键不应该起作用。

数据流:读取 active_tab_idx → 看它是不是有值 → 返回 true 或 false。

调用关系:handle_key_event 在处理左右切换前会先问它,避免普通列表误响应标签页操作。

调用图:被 1 处调用(handle_key_event)。

ListSelectionView::active_items370–375 ↗
fn active_items(&self) -> &[SelectionItem]

作用:拿到当前真正应该显示和操作的选项列表。如果有标签页,就拿当前标签页的选项;否则拿普通选项。

数据流:读取 active_tab_idx、tabs 和 items → 找到当前标签页的 items,找不到就退回主 items → 返回只读选项切片。

调用关系:过滤、确认选择、数字快捷键、禁用检查等函数都通过它访问“当前这一组”数据。

调用图:被 4 处调用(accept, actual_idx_for_enabled_number, apply_filter, enabled_actual_idx)。

ListSelectionView::active_items_mut377–384 ↗
fn active_items_mut(&mut self) -> &mut [SelectionItem]

作用:拿到当前选项列表的可修改版本。主要用于改变勾选开关状态。

数据流:读取当前标签页位置 → 如果有对应标签页就返回它的可变 items,否则返回主 items 的可变切片 → 调用者可以修改里面的项。

调用关系:toggle_selected 用它找到当前项并翻转开关。

调用图:被 1 处调用(toggle_selected)。

ListSelectionView::active_header386–391 ↗
fn active_header(&self) -> &dyn Renderable

作用:拿到当前要显示的头部内容。标签页可以有自己的头部,没有时就用全局头部。

数据流:读取 active_tab_idx 和 tabs → 找标签页 header,找不到则用 self.header → 返回可渲染的头部对象。

调用关系:desired_height 和 render 都会用它,保证高度计算和实际绘制使用同一个头部。

调用图:被 2 处调用(desired_height, render)。

ListSelectionView::max_visible_rows409–411 ↗
fn max_visible_rows(len: usize) -> usize

作用:限制弹窗列表最多显示多少行。这样选项很多时不会把整个终端撑满。

数据流:输入总行数 → 和全局最大行数比较,同时保证至少按 1 行计算 → 返回可见行上限。

调用关系:滚动、翻页、高度计算都会用这个上限,保持列表区域大小稳定。

ListSelectionView::selected_actual_idx413–417 ↗
fn selected_actual_idx(&self) -> Option<usize>

作用:把屏幕上选中的可见行,换算成原始选项列表里的真实下标。搜索过滤后,这一步非常关键。

数据流:读取 state.selected_idx 和 filtered_indices → 用可见下标查真实下标 → 返回真实下标或 None。

调用关系:选择确认、预览回调、移动前后比较、开关判断都靠它避免选错原始项目。

调用图:被 12 处调用(apply_filter, fire_selection_changed, jump_bottom, jump_top, move_down, move_up, page_down, page_up, selected_index, selected_item_has_toggle (+2 more))。

ListSelectionView::apply_filter419–489 ↗
fn apply_filter(&mut self)

作用:根据搜索文字重新计算哪些选项可见,并尽量保留原来的选中项。它是搜索和切换标签页后的核心整理步骤。

数据流:读取搜索状态、当前选项、原选中项和初始选择 → 生成 filtered_indices,挑一个可用选中行,修正滚动范围 → 更新内部筛选、选择和滚动状态,必要时触发选中变化回调。

调用关系:new、handle_key_event、handle_paste、set_search_query、switch_tab 都会调用它;它再调用滚动状态的 clamp_selection 和 ensure_visible 来收尾。

调用图:调用 5 个内部函数(active_items, fire_selection_changed, selected_actual_idx, clamp_selection, ensure_visible);被 4 处调用(handle_key_event, handle_paste, set_search_query, switch_tab);外部调用 1 个(max_visible_rows)。

ListSelectionView::build_rows491–561 ↗
fn build_rows(&self) -> Vec<GenericDisplayRow>

作用:把内部选项数据变成适合绘制的一行行显示数据。它会加上箭头、编号、开关、current/default 标记和描述。

数据流:读取 filtered_indices、当前选项、选中状态和搜索状态 → 为每个可见项构造 GenericDisplayRow → 返回渲染层能直接使用的行列表。

调用关系:desired_height 用它测量列表需要多高,render 用它真正画列表。

调用图:被 2 处调用(desired_height, render)。

ListSelectionView::switch_tab563–585 ↗
fn switch_tab(&mut self, step: isize)

作用:切换到上一个或下一个标签页。切换后会清空搜索,因为不同标签页的内容不一样。

数据流:输入步长,负数表示向左,正数表示向右 → 计算新标签页,清空搜索和滚动,重新过滤并选中可用项 → 更新当前标签页并触发选中变化。

调用关系:handle_key_event 在用户按左右切换键时调用它;它会把后续整理交给 apply_filter。

调用图:调用 4 个内部函数(apply_filter, fire_selection_changed, select_first_enabled_row, reset);被 1 处调用(handle_key_event)。

ListSelectionView::select_first_enabled_row587–593 ↗
fn select_first_enabled_row(&mut self)

作用:选中第一条可用的行。禁用项不能被默认选中。

数据流:读取筛选后的行 → 找第一条未禁用的行,找不到就退回第一行 → 更新 selected_idx,并把滚动位置归零。

调用关系:switch_tab 在切换后没有选中项时调用它,保证界面上有一个合理焦点。

调用图:调用 1 个内部函数(first_enabled_visible_idx);被 1 处调用(switch_tab)。

ListSelectionView::first_enabled_visible_idx595–601 ↗
fn first_enabled_visible_idx(&self) -> Option<usize>

作用:查找当前可见列表里第一条能选的行。

数据流:遍历 filtered_indices → 对照当前选项检查是否启用 → 返回第一个可用可见下标,找不到返回 None。

调用关系:select_first_enabled_row 和 apply_filter 用它决定默认落点。

调用图:被 1 处调用(select_first_enabled_row)。

ListSelectionView::enabled_actual_idx603–608 ↗
fn enabled_actual_idx(&self, actual_idx: usize) -> Option<usize>

作用:确认某个真实下标对应的选项是否可用。可用才返回它自己。

数据流:输入原始选项下标 → 在 active_items 中取出该项并检查禁用状态 → 返回 Some(下标) 或 None。

调用关系:apply_filter 用它判断旧选中项或初始选中项还能不能继续保留。

调用图:调用 1 个内部函数(active_items)。

ListSelectionView::item_is_enabled610–612 ↗
fn item_is_enabled(item: &SelectionItem) -> bool

作用:统一判断一个选项能不能被选择。只要显式禁用,或有禁用原因,就算不可选。

数据流:输入 SelectionItem → 检查 is_disabled 和 disabled_reason → 返回是否启用。

调用关系:过滤默认选择、编号、快捷选择、开关操作等地方都用这个规则,避免各处判断不一致。

ListSelectionView::selected_item_has_toggle614–618 ↗
fn selected_item_has_toggle(&self) -> bool

作用:判断当前选中项是不是一个可以按空格切换的开关项。

数据流:读取当前真实选中下标 → 找到对应选项 → 检查它有 toggle 且没有禁用 → 返回 true 或 false。

调用关系:handle_key_event 在处理空格键前会问它,决定空格是切换开关还是继续输入搜索。

调用图:调用 1 个内部函数(selected_actual_idx);被 1 处调用(handle_key_event)。

ListSelectionView::selected_item_has_toggle_placeholder620–628 ↗
fn selected_item_has_toggle_placeholder(&self) -> bool

作用:判断当前选中项是不是只有开关占位符、但没有真正开关。这样空格在某些情况下会被吃掉,避免误输入。

数据流:读取当前选中项 → 检查没有 toggle、有 toggle_placeholder、且项目启用 → 返回 true 或 false。

调用关系:handle_key_event 用它处理搜索框为空时的空格行为。

调用图:调用 1 个内部函数(selected_actual_idx);被 1 处调用(handle_key_event)。

ListSelectionView::actual_idx_for_enabled_number630–641 ↗
fn actual_idx_for_enabled_number(&self, number: usize) -> Option<usize>

作用:把用户按的数字编号换成真实选项下标。编号只给可用项排,禁用项会被跳过。

数据流:输入数字 → 如果是 0 直接无效;否则遍历当前选项里的可用项,取第 number 个 → 返回真实下标或 None。

调用关系:handle_key_event 在非搜索列表中处理数字快捷选择时调用它。

调用图:调用 1 个内部函数(active_items)。

ListSelectionView::toggle_selected643–660 ↗
fn toggle_selected(&mut self)

作用:翻转当前选中项的开关,并通知外部执行对应动作。

数据流:读取当前选中下标和事件发送器 → 找到当前选项,确认它可用且有 toggle → 反转 is_on,并调用 toggle.action 发事件。

调用关系:handle_key_event 在用户按空格且当前项可切换时调用它。

调用图:调用 2 个内部函数(active_items_mut, selected_actual_idx);被 1 处调用(handle_key_event);外部调用 2 个(item_is_enabled, clone)。

ListSelectionView::move_up662–672 ↗
fn move_up(&mut self)

作用:把选中项向上移动一格,遇到列表头会绕到尾部,并跳过禁用项。

数据流:记录移动前真实选中项 → 根据可见数量移动滚动状态,跳过禁用行,确保新位置在屏幕内 → 如果真实选中项变了,就触发回调。

调用关系:handle_key_event 在上移按键触发时调用它;它把禁用处理交给 skip_disabled_up。

调用图:调用 6 个内部函数(fire_selection_changed, selected_actual_idx, skip_disabled_up, visible_len, ensure_visible, move_up_wrap);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

ListSelectionView::move_down674–684 ↗
fn move_down(&mut self)

作用:把选中项向下移动一格,遇到列表尾会绕到头部,并跳过禁用项。

数据流:记录移动前真实选中项 → 移动到下一行,跳过禁用行,修正滚动 → 如果选中项变化就通知外部。

调用关系:handle_key_event 在下移按键触发时调用它;它把禁用处理交给 skip_disabled_down。

调用图:调用 6 个内部函数(fire_selection_changed, selected_actual_idx, skip_disabled_down, visible_len, ensure_visible, move_down_wrap);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

ListSelectionView::page_up686–696 ↗
fn page_up(&mut self)

作用:向上翻一页,但不会绕圈。适合长列表快速移动。

数据流:记录旧选中项 → 按当前可见行数向上跳,若落在禁用项上就向上找可用项,再保持可见 → 选中变化时触发回调。

调用关系:handle_key_event 在 PageUp 或对应自定义快捷键时调用它。

调用图:调用 6 个内部函数(fire_selection_changed, selected_actual_idx, skip_disabled_up_clamped, visible_len, ensure_visible, page_up_clamped);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

ListSelectionView::page_down698–708 ↗
fn page_down(&mut self)

作用:向下翻一页,但不会绕圈。列表末尾有禁用项时会停在最近的可用项。

数据流:记录旧选中项 → 向下跳一页,必要时从落点附近找可用项,修正滚动 → 选中变化时触发回调。

调用关系:handle_key_event 在 PageDown 或对应快捷键时调用它。

调用图:调用 6 个内部函数(fire_selection_changed, selected_actual_idx, skip_disabled_down_clamped, visible_len, ensure_visible, page_down_clamped);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

ListSelectionView::jump_top710–720 ↗
fn jump_top(&mut self)

作用:跳到列表顶部,并避开顶部附近的禁用项。

数据流:记录旧选中项 → 把滚动状态跳到开头,若落在禁用项就向下找可用项 → 更新可见区域并可能触发回调。

调用关系:handle_key_event 在跳到顶部快捷键触发时调用它。

调用图:调用 6 个内部函数(fire_selection_changed, selected_actual_idx, skip_disabled_down_clamped, visible_len, ensure_visible, jump_top);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

ListSelectionView::jump_bottom722–732 ↗
fn jump_bottom(&mut self)

作用:跳到列表底部,并避开底部附近的禁用项。

数据流:记录旧选中项 → 把滚动状态跳到底部,若落在禁用项就向上找可用项 → 更新可见区域并可能触发回调。

调用关系:handle_key_event 在跳到底部快捷键触发时调用它。

调用图:调用 6 个内部函数(fire_selection_changed, selected_actual_idx, skip_disabled_up_clamped, visible_len, ensure_visible, jump_bottom);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

ListSelectionView::fire_selection_changed734–740 ↗
fn fire_selection_changed(&self)

作用:在高亮项变化时通知外部。比如主题选择器可以边移动边实时预览主题。

数据流:读取 on_selection_changed 回调和当前真实选中下标 → 如果两者都有,就把下标和事件发送器传给回调 → 外部可能因此发出应用事件。

调用关系:移动、翻页、跳转、切换标签页和过滤后都会调用它。

调用图:调用 1 个内部函数(selected_actual_idx);被 8 处调用(apply_filter, jump_bottom, jump_top, move_down, move_up, page_down, page_up, switch_tab)。

ListSelectionView::accept742–772 ↗
fn accept(&mut self)

作用:确认当前选中项。可选项会执行它的动作;没有匹配项时回车会按取消处理。

数据流:读取当前可见选中项并换成真实下标 → 若项目可用,记录最后选择,执行 actions,按配置完成或等待子弹窗;若没有选中项,则调用取消回调并标记取消 → 更新 completion 或 dismiss_after_child_accept。

调用关系:handle_key_event 在确认键或快捷选择时调用它;上层之后通过 completion 或 take_last_selected_index 读取结果。

调用图:调用 1 个内部函数(active_items);被 1 处调用(handle_key_event)。

ListSelectionView::set_search_query775–778 ↗
fn set_search_query(&mut self, query: String)

作用:测试用的入口,直接设置搜索文字。正常用户是通过键盘输入改变搜索。

数据流:输入新搜索字符串 → 写入 search_query → 调用 apply_filter 更新可见项和选中项。

调用关系:只在测试配置下编译,测试用它快速模拟搜索状态。

调用图:调用 1 个内部函数(apply_filter)。

ListSelectionView::take_last_selected_index780–782 ↗
fn take_last_selected_index(&mut self) -> Option<usize>

作用:取走最近一次确认选择的真实下标。取走后内部会清空,避免重复读取同一次选择。

数据流:读取 last_selected_actual_idx → 返回其中的值,同时把内部值设为 None → 调用者得到一次性结果。

调用关系:上层选择流程和测试会在确认后调用它来知道用户选了哪一项。

调用图:被 2 处调用(handle_key_event, selected_choice)。

ListSelectionView::rows_width784–786 ↗
fn rows_width(total_width: u16) -> u16

作用:计算列表行可用宽度,给左右边界留出一点空间。

数据流:输入总宽度 → 安全减去 2 → 返回行内容宽度。

调用关系:desired_height 和 render 间接使用同一套宽度规则,保证测量和绘制一致。

ListSelectionView::clear_to_terminal_bg788–807 ↗
fn clear_to_terminal_bg(buf: &mut Buffer, area: Rect)

作用:把指定区域清成终端默认背景。它会同时清掉字符和样式。

数据流:输入屏幕缓冲区和区域 → 把区域裁剪到缓冲区范围内 → 每个格子写空格并重置样式。

调用关系:render 在画右侧预览或堆叠内容前调用它,避免菜单背景污染预览区域。

调用图:外部调用 2 个(area, reset)。

ListSelectionView::force_bg_to_terminal_bg809–826 ↗
fn force_bg_to_terminal_bg(buf: &mut Buffer, area: Rect)

作用:只把指定区域的背景色改回终端默认,不清掉字符。用于保留预览文字但去掉背景色。

数据流:输入缓冲区和区域 → 裁剪到缓冲区内 → 遍历格子,把背景色设为 Reset。

调用关系:render 在右侧并排预览后调用它;如果配置要求保留背景色,就不会调用。

调用图:外部调用 1 个(area)。

ListSelectionView::stacked_side_content828–832 ↗
fn stacked_side_content(&self) -> &dyn Renderable

作用:选择窄屏时放在列表下面的内容。若没有专门的窄屏版本,就复用右侧预览内容。

数据流:读取 stacked_side_content 和 side_content → 优先返回 stacked_side_content,否则返回 side_content → 输出可渲染对象引用。

调用关系:desired_height 和 render 在不能左右布局时用它。

调用图:被 2 处调用(desired_height, render)。

ListSelectionView::side_layout_width836–843 ↗
fn side_layout_width(&self, content_width: u16) -> Option<u16>

作用:判断当前内容宽度是否能启用右侧并排预览,并返回右侧预览宽度。

数据流:输入内容宽度 → 把宽度模式和最小宽度交给 side_by_side_layout_widths → 返回 side_width 或 None。

调用关系:desired_height 和 render 用它决定右侧内容是放旁边还是放下面。

调用图:调用 1 个内部函数(side_by_side_layout_widths);被 2 处调用(desired_height, render)。

ListSelectionView::skip_disabled_down845–854 ↗
fn skip_disabled_down(&mut self)

作用:向下移动时跳过禁用行,并允许从底部绕回顶部。

数据流:读取当前可见长度 → 如果当前行禁用,就反复向下移动,最多检查一圈 → 最终停在可用行或原地。

调用关系:move_down 调用它,让普通向下导航不会停在不可选项上。

调用图:调用 3 个内部函数(selected_visible_idx_is_disabled, visible_len, move_down_wrap);被 1 处调用(move_down)。

ListSelectionView::skip_disabled_up856–865 ↗
fn skip_disabled_up(&mut self)

作用:向上移动时跳过禁用行,并允许从顶部绕回底部。

数据流:读取当前可见长度 → 如果当前行禁用,就反复向上移动,最多检查一圈 → 最终停在可用行或原地。

调用关系:move_up 调用它,让普通向上导航不会停在不可选项上。

调用图:调用 3 个内部函数(selected_visible_idx_is_disabled, visible_len, move_up_wrap);被 1 处调用(move_up)。

ListSelectionView::skip_disabled_down_clamped867–884 ↗
fn skip_disabled_down_clamped(&mut self)

作用:翻页或跳顶部后,如果落在禁用行,就找附近的可用行,但不做环形移动。

数据流:读取当前可见选中位置 → 如果禁用,先向下找可用行,找不到再向上找 → 更新 selected_idx。

调用关系:page_down 和 jump_top 调用它,避免翻页时突然绕到很远的位置。

调用图:调用 2 个内部函数(visible_idx_is_disabled, visible_len);被 2 处调用(jump_top, page_down)。

ListSelectionView::skip_disabled_up_clamped886–900 ↗
fn skip_disabled_up_clamped(&mut self)

作用:翻页或跳底部后,如果落在禁用行,就往上找可用行,找不到再往下找。

数据流:读取当前选中位置 → 如果禁用,先向上找可用行,找不到再向下找 → 更新 selected_idx。

调用关系:page_up 和 jump_bottom 调用它,特别处理列表尾部一串禁用项的情况。

调用图:调用 2 个内部函数(visible_idx_is_disabled, visible_len);被 2 处调用(jump_bottom, page_up)。

ListSelectionView::selected_visible_idx_is_disabled902–906 ↗
fn selected_visible_idx_is_disabled(&self) -> bool

作用:判断当前屏幕选中行是不是禁用行。

数据流:读取 state.selected_idx → 如果有选中位置,就交给 visible_idx_is_disabled 检查 → 返回 true 或 false。

调用关系:skip_disabled_down 和 skip_disabled_up 用它决定是否继续跳过。

调用图:被 2 处调用(skip_disabled_down, skip_disabled_up)。

ListSelectionView::visible_idx_is_disabled908–913 ↗
fn visible_idx_is_disabled(&self, idx: usize) -> bool

作用:判断某个可见行是否对应禁用选项。

数据流:输入可见下标 → 通过 filtered_indices 找真实选项 → 检查 disabled_reason 或 is_disabled → 返回是否禁用。

调用关系:各种跳过禁用项的函数都用它作为统一判断。

调用图:被 2 处调用(skip_disabled_down_clamped, skip_disabled_up_clamped)。

ListSelectionView::handle_key_event917–1025 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:处理用户在选择框里按下的键。这是列表交互的总入口。

数据流:输入一个键盘事件 → 按键映射判断它是移动、翻页、切标签、搜索输入、空格切换、取消、确认、数字快捷选择等 → 更新搜索、选择、开关、完成状态,或触发动作。

调用关系:底部面板系统收到键盘事件后会调用它;它再分发给 move_up、move_down、apply_filter、toggle_selected、accept、on_ctrl_c 等具体函数。

调用图:调用 15 个内部函数(accept, apply_filter, jump_bottom, jump_top, move_down, move_up, on_ctrl_c, page_down, page_up, selected_item_has_toggle (+5 more));被 1 处调用(handle_key_event)。

ListSelectionView::handle_paste1027–1037 ↗
fn handle_paste(&mut self, pasted: String) -> bool

作用:处理用户粘贴到搜索框里的文字。只有可搜索列表才接受粘贴。

数据流:输入粘贴字符串 → 如果列表不可搜索直接返回 false;否则清理粘贴内容,空白无效;有效内容追加到 search_query → 重新过滤并返回 true。

调用关系:终端粘贴事件会走这里;它使用 normalize_pasted_search_query 防止换行和纯空白破坏搜索。

调用图:调用 2 个内部函数(apply_filter, normalize_pasted_search_query)。

ListSelectionView::is_complete1039–1041 ↗
fn is_complete(&self) -> bool

作用:告诉外部这个弹窗是否已经结束。结束可能是确认,也可能是取消。

数据流:读取 completion → 判断是否有值 → 返回布尔值。

调用关系:上层选择流程会轮询或检查它,决定是否关闭当前视图。

调用图:被 1 处调用(selected_choice)。

ListSelectionView::completion1043–1045 ↗
fn completion(&self) -> Option<ViewCompletion>

作用:返回弹窗的结束结果。外部可以知道用户是接受还是取消。

数据流:读取 completion → 原样返回 Option<ViewCompletion> → 不修改内部状态。

调用关系:底部面板框架在视图完成后读取它来决定后续流程。

ListSelectionView::dismiss_after_child_accept1047–1049 ↗
fn dismiss_after_child_accept(&self) -> bool

作用:告诉外部:如果子弹窗确认了,父弹窗是否也该关闭。

数据流:读取 dismiss_after_child_accept → 返回当前标记 → 不改变状态。

调用关系:accept 会在某些选项配置下设置这个标记,上层弹窗栈会读取它。

ListSelectionView::clear_dismiss_after_child_accept1051–1053 ↗
fn clear_dismiss_after_child_accept(&mut self)

作用:清除“子弹窗确认后关闭父弹窗”的标记。用完就要清掉,避免影响下一次。

数据流:没有外部输入 → 把 dismiss_after_child_accept 设为 false → 内部状态被重置。

调用关系:上层处理完这个标记后会调用它。

ListSelectionView::view_id1055–1057 ↗
fn view_id(&self) -> Option<&'static str>

作用:返回这个视图的可选身份标识。外部可以用它判断当前是不是某个特定弹窗。

数据流:读取 view_id → 返回 Option<&'static str> → 不修改状态。

调用关系:弹窗替换或查找逻辑会用它识别活跃选择框。

ListSelectionView::selected_index1059–1061 ↗
fn selected_index(&self) -> Option<usize>

作用:返回当前选中项在原始列表里的真实下标。

数据流:调用 selected_actual_idx → 得到真实下标或 None → 返回给外部。

调用关系:底部面板统一接口需要这个函数,外部不用知道搜索后的下标映射细节。

调用图:调用 1 个内部函数(selected_actual_idx)。

ListSelectionView::active_tab_id1063–1065 ↗
fn active_tab_id(&self) -> Option<&str>

作用:返回当前标签页的 id。没有标签页时返回空。

数据流:读取 active_tab_idx 和 tabs → 找到当前标签页 → 返回它的 id 字符串切片或 None。

调用关系:active_footer_hint 和外部接口都会用它判断当前分组。

调用图:被 1 处调用(active_footer_hint)。

ListSelectionView::prefer_esc_to_handle_key_event1067–1069 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

作用:告诉外层:Esc 键应该优先交给这个视图自己处理。这样取消回调能正常执行。

数据流:没有输入 → 固定返回 true → 不改变状态。

调用关系:底部面板的按键分发逻辑会根据它决定 Esc 的处理顺序。

ListSelectionView::on_ctrl_c1071–1080 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理 Ctrl+C 或等价取消动作。允许取消时,它会运行取消回调并标记弹窗已取消。

数据流:读取 allow_cancel → 如果不允许,返回 NotHandled;如果允许,调用 on_cancel,设置 completion 为 Cancelled → 返回 Handled。

调用关系:handle_key_event 在取消快捷键触发时调用它;外层也可通过统一取消接口调用。

调用图:被 1 处调用(handle_key_event)。

ListSelectionView::desired_height1084–1140 ↗
fn desired_height(&self, width: u16) -> u16

作用:计算这个弹窗在给定宽度下希望占多高。先算高度再绘制,可以避免界面挤坏。

数据流:输入可用宽度 → 计算内容宽、头部高、标签高、搜索框、列表换行高度、侧边内容高度、页脚高度 → 返回总高度。

调用关系:渲染系统和测试会先调用它决定绘制区域;它使用 build_rows、active_header、side_layout_width 等保证测量规则和 render 一致。

调用图:调用 10 个内部函数(active_footer_hint, active_header, build_rows, side_layout_width, stacked_side_content, popup_content_width, new, measure_rows_height_with_col_width_mode, wrap_styled_line, tab_bar_height);被 4 处调用(desired_height, render_lines_with_width, render_picker_from_view, render_lines);外部调用 2 个(rows_width, from)。

ListSelectionView::render1142–1370 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把整个选择弹窗画到终端缓冲区里。它负责标题、标签页、搜索框、列表、右侧预览、底部提示。

数据流:输入绘制区域和屏幕缓冲区 → 画菜单背景,切分布局,构造行数据,按宽度选择左右或上下预览,画列表和页脚 → 缓冲区被填上最终画面。

调用关系:终端 UI 每次刷新时调用它;它和 desired_height 使用同一批辅助函数,避免算出来的高度和实际画法不一致。

调用图:调用 13 个内部函数(active_footer_hint, active_header, build_rows, side_layout_width, stacked_side_content, popup_content_width, new, measure_rows_height_with_col_width_mode, render_menu_surface, render_rows_single_line_with_col_width_mode (+3 more));被 5 处调用(render, render_lines_in_area, render_picker_from_view, render_ref, render_lines);外部调用 12 个(Fill, Length, Max, vertical, from, new, new, clear_to_terminal_bg, force_bg_to_terminal_bg, rows_width (+2 more))。

tests::MarkerRenderable::render1395–1403 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:测试用的假渲染器,把指定区域填成同一个标记字符。方便检查侧边内容画到了哪里。

数据流:输入区域和缓冲区 → 遍历区域内格子,写入 marker → 缓冲区出现一块标记区域。

调用关系:多个侧边预览布局测试用它模拟真实预览内容。

调用图:外部调用 1 个(area)。

tests::MarkerRenderable::desired_height1405–1407 ↗
fn desired_height(&self, _width: u16) -> u16

作用:测试用,报告假渲染器想要的高度。

数据流:忽略宽度输入 → 返回预设 height → 不改状态。

调用关系:高度计算测试通过它控制侧边内容占几行。

tests::StyledMarkerRenderable::render1417–1425 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:测试用的带样式假渲染器,会画字符并设置颜色样式。主要检查背景色是否被保留或清除。

数据流:输入区域和缓冲区 → 把每个格子写成 marker,并应用 style → 缓冲区里出现带样式的标记。

调用关系:背景保留相关测试用它验证 preserve_side_content_bg 的效果。

调用图:外部调用 1 个(area)。

tests::StyledMarkerRenderable::desired_height1427–1429 ↗
fn desired_height(&self, _width: u16) -> u16

作用:测试用,返回带样式假渲染器需要的高度。

数据流:忽略宽度 → 返回 height → 不改状态。

调用关系:配合 StyledMarkerRenderable::render 用在侧边内容测试里。

tests::new_view1432–1434 ↗
fn new_view(params: SelectionViewParams, tx: AppEventSender) -> ListSelectionView

作用:测试辅助函数,用默认按键配置快速创建 ListSelectionView。

数据流:输入参数和事件发送器 → 取默认运行时键位表里的 list 部分,调用 ListSelectionView::new → 返回视图。

调用关系:很多测试用它少写重复样板代码。

调用图:调用 2 个内部函数(new, defaults)。

tests::make_selection_view1436–1465 ↗
fn make_selection_view(subtitle: Option<&str>) -> ListSelectionView

作用:测试辅助函数,创建一个带标题、可选副标题和两个选项的标准选择框。

数据流:输入可选副标题 → 建立测试事件通道和两个 SelectionItem → 调用 new_view → 返回视图。

调用关系:标题间距相关快照测试用它生成稳定画面。

调用图:调用 2 个内部函数(new, standard_popup_hint_line);外部调用 3 个(default, new_view, vec!)。

tests::render_lines1467–1469 ↗
fn render_lines(view: &ListSelectionView) -> String

作用:测试辅助函数,用默认宽度把视图渲染成多行字符串。

数据流:输入视图 → 使用宽度 48 调用 render_lines_with_width → 返回文本画面。

调用关系:快照测试用它比较终端输出是否变化。

调用图:外部调用 1 个(render_lines_with_width)。

tests::render_lines_with_width1471–1473 ↗
fn render_lines_with_width(view: &ListSelectionView, width: u16) -> String

作用:测试辅助函数,用指定宽度渲染视图,并自动使用视图期望高度。

数据流:输入视图和宽度 → 调用 desired_height 得到高度,再调用 render_lines_in_area → 返回文本画面。

调用关系:大多数布局测试通过它观察不同宽度下的渲染结果。

调用图:调用 1 个内部函数(desired_height);外部调用 1 个(render_lines_in_area)。

tests::render_lines_in_area1475–1495 ↗
fn render_lines_in_area(view: &ListSelectionView, width: u16, height: u16) -> String

作用:测试辅助函数,把视图画进指定大小的假缓冲区,再转成字符串。

数据流:输入视图、宽度和高度 → 创建 Buffer,调用 render,逐格读取字符 → 返回用换行连接的字符串。

调用关系:快照测试和断言测试都用它拿到可比较的终端画面。

调用图:调用 1 个内部函数(render);外部调用 2 个(empty, new)。

tests::description_col1497–1504 ↗
fn description_col(rendered: &str, item_marker: &str, description: &str) -> usize

作用:测试辅助函数,找某个描述文字出现在第几列。用来判断列宽是否稳定。

数据流:输入渲染文本、行标记和描述 → 找到同时包含两者的行,再找描述起始位置 → 返回列号。

调用关系:列宽模式和滚动不抖动的测试用它比较前后列位置。

tests::make_scrolling_width_items1506–1522 ↗
fn make_scrolling_width_items() -> Vec<SelectionItem>

作用:测试辅助函数,生成一组用于滚动和列宽测试的选项,其中最后一项名字特别长。

数据流:没有输入 → 创建 8 个普通项和 1 个长名字项 → 返回选项列表。

调用关系:列宽随滚动变化的测试用它制造容易暴露问题的数据。

调用图:外部调用 1 个(default)。

tests::render_before_after_scroll_snapshot1524–1545 ↗
fn render_before_after_scroll_snapshot(col_width_mode: ColumnWidthMode, width: u16) -> String

作用:测试辅助函数,生成滚动前和滚动后的渲染对比文本。

数据流:输入列宽模式和宽度 → 创建视图,先渲染一次,连续按下向下键,再渲染一次 → 返回合并后的对比字符串。

调用关系:三个列宽模式的快照测试都用它。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 5 个(default, from, format!, make_scrolling_width_items, render_lines_with_width)。

tests::renders_blank_line_between_title_and_items_without_subtitle1548–1554 ↗
fn renders_blank_line_between_title_and_items_without_subtitle()

作用:测试没有副标题时,标题和选项之间仍然有正确空行。

数据流:创建无副标题视图 → 渲染成文本 → 和快照比较。

调用关系:防止后续改布局时把标题和列表挤在一起。

调用图:外部调用 2 个(assert_snapshot!, make_selection_view)。

tests::renders_blank_line_between_subtitle_and_items1557–1560 ↗
fn renders_blank_line_between_subtitle_and_items()

作用:测试有副标题时,副标题和选项之间的间距正确。

数据流:创建带副标题视图 → 渲染成文本 → 和快照比较。

调用关系:和无副标题测试一起保护标题区域布局。

调用图:外部调用 2 个(assert_snapshot!, make_selection_view)。

tests::theme_picker_subtitle_uses_fallback_text_in_94x35_terminal1563–1577 ↗
fn theme_picker_subtitle_uses_fallback_text_in_94x35_terminal()

作用:测试主题选择器在 94x35 的终端大小下会显示可用的提示文字。

数据流:构造主题选择参数和视图 → 在指定区域渲染 → 检查文本中包含移动预览提示。

调用关系:确保主题选择器和这个通用列表视图配合时,小窗口也有清楚说明。

调用图:调用 2 个内部函数(new, build_theme_picker_params);外部调用 4 个(assert!, home_dir, new_view, render_lines_in_area)。

tests::theme_picker_enables_side_content_background_preservation1580–1590 ↗
fn theme_picker_enables_side_content_background_preservation()

作用:测试主题选择器会开启右侧预览背景保留。主题差异预览需要颜色背景才看得清。

数据流:构造主题选择参数 → 检查 preserve_side_content_bg 为 true → 测试通过或失败。

调用关系:保护主题选择器对 ListSelectionView 的配置不被误改。

调用图:调用 1 个内部函数(build_theme_picker_params);外部调用 1 个(assert!)。

tests::preserve_side_content_bg_keeps_rendered_background_colors1593–1633 ↗
fn preserve_side_content_bg_keeps_rendered_background_colors()

作用:测试开启背景保留时,右侧内容自己画的背景色不会被清掉。

数据流:创建带蓝色背景标记的侧边内容视图 → 渲染到缓冲区 → 找到标记字符并检查背景仍是蓝色。

调用关系:验证 render 中 preserve_side_content_bg 分支的行为。

调用图:调用 1 个内部函数(new);外部调用 8 个(new, empty, default, new, default, assert_eq!, new_view, vec!)。

tests::renders_search_query_line_when_enabled1668–1696 ↗
fn renders_search_query_line_when_enabled()

作用:测试开启搜索后,搜索文字会显示在搜索行里。

数据流:创建可搜索视图 → 直接设置搜索词 filters → 渲染并检查文本包含 filters。

调用关系:覆盖 set_search_query、apply_filter 和 render 的搜索框显示路径。

调用图:调用 2 个内部函数(new, standard_popup_hint_line);外部调用 5 个(default, assert!, new_view, render_lines, vec!)。

tests::paste_appends_to_search_query_and_filters_items1699–1728 ↗
fn paste_appends_to_search_query_and_filters_items()

作用:测试粘贴搜索内容会追加到搜索框,并筛出匹配项。

数据流:创建两条可搜索选项 → 先输入 f,再粘贴剩余文本和换行 → 检查 search_query、filtered_indices 和选中项。

调用关系:验证 handle_paste 会清理粘贴内容并调用 apply_filter。

调用图:调用 1 个内部函数(new);外部调用 7 个(default, Char, new, assert!, assert_eq!, new_view, vec!)。

tests::whitespace_only_paste_is_ignored1731–1751 ↗
fn whitespace_only_paste_is_ignored()

作用:测试只包含空白的粘贴不会影响搜索。

数据流:创建可搜索视图 → 粘贴空格、换行和制表符 → 检查返回 false,搜索词和筛选结果不变。

调用关系:保护 normalize_pasted_search_query 和 handle_paste 的无效输入处理。

调用图:调用 1 个内部函数(new);外部调用 5 个(default, assert!, assert_eq!, new_view, vec!)。

tests::tabbed_view_preserves_current_row_on_initial_selection_and_tab_switch1806–1863 ↗
fn tabbed_view_preserves_current_row_on_initial_selection_and_tab_switch()

作用:测试标签页里标记为 current 的行会被优先选中,切换标签后也保持类似位置。

数据流:创建两个标签页,每个都有 current 行 → 初始到 beta,再切到 alpha → 检查真实选中下标都是 current 行。

调用关系:保护 apply_filter 中默认选中 current 项的逻辑。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 4 个(default, from, assert_eq!, vec!)。

tests::space_appends_to_active_search_instead_of_toggling_selected_item1866–1903 ↗
fn space_appends_to_active_search_instead_of_toggling_selected_item()

作用:测试搜索框已有内容时,空格应该继续输入搜索,而不是切换当前项开关。

数据流:创建可搜索且带开关的视图 → 设置搜索词 plugin,按空格 → 检查搜索词变成 plugin 空格,开关和事件都没变。

调用关系:验证 handle_key_event 对空格键的优先级处理。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 6 个(default, Char, new, assert!, assert_eq!, vec!)。

tests::single_line_row_display_truncates_instead_of_wrapping1906–1958 ↗
fn single_line_row_display_truncates_instead_of_wrapping()

作用:测试单行显示模式会用省略号截断长文本,而不是换行撑高。

数据流:创建单行视图和默认换行视图 → 用窄宽度渲染单行视图并比较两者高度 → 检查有省略号且单行高度更小。

调用关系:保护 SelectionRowDisplay::SingleLine 在 desired_height 和 render 中的一致行为。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 4 个(default, assert!, render_lines_with_width, vec!)。

tests::name_column_width_override_moves_description_column_right1961–2025 ↗
fn name_column_width_override_moves_description_column_right()

作用:测试手动指定名称列宽会把描述列往右推。

数据流:创建自动列宽视图和指定 name_column_width 的视图 → 渲染并找 desc 所在列 → 检查指定列宽的描述更靠右。

调用关系:覆盖 ColumnWidthConfig 与 render_rows_single_line 的配合。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 5 个(default, assert!, description_col, render_lines_with_width, vec!)。

tests::enter_with_no_matches_triggers_cancel_callback2028–2056 ↗
fn enter_with_no_matches_triggers_cancel_callback()

作用:测试搜索没有匹配项时按回车,会按取消处理并触发取消回调。

数据流:创建可搜索视图和取消回调 → 设置无匹配搜索词,按 Enter → 检查视图完成且事件通道收到取消事件。

调用关系:验证 accept 在 selected_actual_idx 为空时的特殊路径。

调用图:调用 1 个内部函数(new);外部调用 7 个(new, default, from, assert!, panic!, new_view, vec!)。

tests::move_down_without_selection_change_does_not_fire_callback2059–2085 ↗
fn move_down_without_selection_change_does_not_fire_callback()

作用:测试只有一个选项时按下移,不会重复触发选中变化回调。

数据流:创建单项视图和回调 → 清空已有事件,按 Down → 检查没有新事件。

调用关系:保护 move_down 中“真实选中项没变就不通知”的判断。

调用图:调用 1 个内部函数(new);外部调用 6 个(new, default, from, assert!, new_view, vec!)。

tests::disabled_current_rows_skip_default_selection_and_number_shortcuts2088–2143 ↗
fn disabled_current_rows_skip_default_selection_and_number_shortcuts()

作用:测试禁用项即使标记为 current 也不会默认选中,数字快捷键也只给可用项编号。

数据流:创建包含禁用项和可用项的列表 → 检查默认选中 Alpha,渲染编号跳过禁用项,再按数字 2 → 检查选中真实下标是 Beta。

调用关系:覆盖 item_is_enabled、build_rows、actual_idx_for_enabled_number 和 accept 的协作。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 7 个(default, Char, new, assert!, assert_eq!, render_lines_with_width, vec!)。

tests::c0_ctrl_p_respects_unbound_list_move_up2146–2175 ↗
fn c0_ctrl_p_respects_unbound_list_move_up()

作用:测试取消绑定上移键后,Ctrl+P 这种控制字符不会偷偷触发上移或进入搜索。

数据流:创建可搜索视图并清空 move_up 绑定 → 输入 Ctrl+P 字符 → 检查选中项和搜索词都没变。

调用关系:保护 handle_key_event 对自定义键位和控制字符的处理。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 5 个(default, Char, new, assert_eq!, vec!)。

tests::c0_ctrl_n_respects_unbound_list_move_down2178–2206 ↗
fn c0_ctrl_n_respects_unbound_list_move_down()

作用:测试取消绑定下移键后,Ctrl+N 不会触发下移。

数据流:创建可搜索视图并清空 move_down 绑定 → 输入 Ctrl+N 字符 → 检查仍选中第一项且搜索为空。

调用关系:和 Ctrl+P 测试一起确保控制字符只按键位表生效。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 5 个(default, Char, new, assert_eq!, vec!)。

tests::c0_ctrl_p_respects_remapped_list_move_down2209–2237 ↗
fn c0_ctrl_p_respects_remapped_list_move_down()

作用:测试把 Ctrl+P 重映射成下移后,它确实按新绑定执行。

数据流:创建视图,清空 move_up,并把 move_down 设为 Ctrl+P → 输入 Ctrl+P → 检查选中第二项。

调用关系:验证 handle_key_event 使用 ListKeymap,而不是写死按键。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 5 个(default, Char, new, assert_eq!, vec!)。

tests::page_and_jump_navigation_use_list_keymap2240–2276 ↗
fn page_and_jump_navigation_use_list_keymap()

作用:测试翻页和跳顶跳底也遵守自定义键位表。

数据流:创建 12 项列表并重设 page/jump 快捷键 → 先按默认 PageDown 检查无效,再按自定义键 → 检查选中位置符合预期。

调用关系:覆盖 handle_key_event 到 page_down、page_up、jump_bottom、jump_top 的自定义键流程。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 6 个(default, Char, from, new, assert_eq!, vec!)。

tests::page_and_jump_navigation_skip_trailing_disabled_rows_without_wrapping2279–2308 ↗
fn page_and_jump_navigation_skip_trailing_disabled_rows_without_wrapping()

作用:测试翻页和跳到底部时,如果末尾都是禁用项,会停在最后一个可用项,而不是绕回去。

数据流:创建 12 项列表,后 4 项禁用 → 按 PageDown 和 End → 检查选中第 7 项并且滚动范围包含它。

调用关系:验证 skip_disabled_down_clamped 和 skip_disabled_up_clamped 的边界行为。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 4 个(default, from, assert!, assert_eq!)。

tests::wraps_long_option_without_overflowing_columns2311–2349 ↗
fn wraps_long_option_without_overflowing_columns()

作用:测试特别长的选项文字会换行并对齐,不会挤爆列宽。

数据流:创建包含超长命令的选项 → 用宽度 60 渲染 → 检查命令被换行且缩进在编号后方。

调用关系:保护 build_rows 中 wrap_indent 和行渲染函数的配合。

调用图:调用 1 个内部函数(new);外部调用 5 个(default, assert!, new_view, render_lines_with_width, vec!)。

tests::width_changes_do_not_hide_rows2352–2403 ↗
fn width_changes_do_not_hide_rows()

作用:测试在一段宽度范围内改变窗口宽度时,第三个选项不会消失。

数据流:创建三个带描述的模型选项 → 遍历宽度 60 到 90 渲染 → 收集缺少第三项的宽度并要求为空。

调用关系:防止高度测量和换行渲染不一致导致行被裁掉。

调用图:调用 1 个内部函数(new);外部调用 6 个(default, new, assert!, new_view, render_lines_with_width, vec!)。

tests::narrow_width_keeps_all_rows_visible2406–2431 ↗
fn narrow_width_keeps_all_rows_visible()

作用:测试很窄的窗口下也能看到所有短选项。

数据流:创建三条短名称选项 → 用宽度 24 渲染 → 检查第三项编号存在。

调用关系:补充保护窄屏下 desired_height 和 render 的配合。

调用图:调用 1 个内部函数(new);外部调用 4 个(default, assert!, new_view, render_lines_with_width)。

tests::snapshot_model_picker_width_802434–2478 ↗
fn snapshot_model_picker_width_80()

作用:用快照固定模型选择器在 80 列宽下的显示效果。

数据流:创建三个模型选项视图 → 用宽度 80 渲染 → 和快照比较。

调用关系:作为常见真实弹窗的回归测试。

调用图:调用 1 个内部函数(new);外部调用 4 个(default, assert_snapshot!, new_view, vec!)。

tests::snapshot_narrow_width_preserves_third_option2481–2505 ↗
fn snapshot_narrow_width_preserves_third_option()

作用:用快照固定窄屏下三行选项都存在的画面。

数据流:创建三项视图 → 用宽度 24 渲染 → 和快照比较。

调用关系:和 narrow_width_keeps_all_rows_visible 一起保护窄屏布局。

调用图:调用 1 个内部函数(new);外部调用 3 个(default, assert_snapshot!, new_view)。

tests::snapshot_auto_visible_col_width_mode_scroll_behavior2508–2513 ↗
fn snapshot_auto_visible_col_width_mode_scroll_behavior()

作用:用快照记录 AutoVisible 列宽模式滚动前后的样子。

数据流:调用 render_before_after_scroll_snapshot 生成对比文本 → 和快照比较 → 发现列宽行为变化时测试失败。

调用关系:覆盖只测可见行宽度的列宽模式。

调用图:外部调用 1 个(assert_snapshot!)。

tests::snapshot_auto_all_rows_col_width_mode_scroll_behavior2516–2521 ↗
fn snapshot_auto_all_rows_col_width_mode_scroll_behavior()

作用:用快照记录 AutoAllRows 列宽模式滚动前后的样子。

数据流:生成该模式下滚动前后文本 → 和快照比较 → 固定预期输出。

调用关系:覆盖按所有行测量列宽的稳定模式。

调用图:外部调用 1 个(assert_snapshot!)。

tests::snapshot_fixed_col_width_mode_scroll_behavior2524–2529 ↗
fn snapshot_fixed_col_width_mode_scroll_behavior()

作用:用快照记录 Fixed 列宽模式滚动前后的样子。

数据流:生成固定列宽模式的滚动对比 → 和快照比较 → 固定 30/70 分列显示。

调用关系:覆盖固定比例列宽模式。

调用图:外部调用 1 个(assert_snapshot!)。

tests::auto_all_rows_col_width_does_not_shift_when_scrolling2532–2564 ↗
fn auto_all_rows_col_width_does_not_shift_when_scrolling()

作用:测试 AutoAllRows 模式滚动后描述列不会左右抖动。

数据流:创建带长名字项的视图 → 记录滚动前描述列,向下滚动,再记录滚动后描述列 → 检查两者相等。

调用关系:验证测量所有行的列宽策略确实稳定。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 7 个(default, from, assert!, assert_eq!, description_col, make_scrolling_width_items, render_lines_with_width)。

tests::fixed_col_width_is_30_70_and_does_not_shift_when_scrolling2567–2599 ↗
fn fixed_col_width_is_30_70_and_does_not_shift_when_scrolling()

作用:测试 Fixed 模式使用 30/70 分列,并且滚动后不变。

数据流:创建固定列宽视图 → 计算期望描述列位置,渲染并比较,滚动后再比较 → 确认列位置稳定。

调用关系:保护固定列宽模式的数学规则。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 6 个(default, from, assert_eq!, description_col, make_scrolling_width_items, render_lines_with_width)。

tests::side_layout_width_half_uses_exact_split2602–2626 ↗
fn side_layout_width_half_uses_exact_split()

作用:测试右侧宽度 Half 模式会按内容宽度减间隔后精确平分。

数据流:创建带右侧内容的视图 → 用内容宽度 120 计算期望值 → 比较 side_layout_width 返回值。

调用关系:验证 side_by_side_layout_widths 的 Half 分支。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, default, assert_eq!, new_view, vec!)。

tests::side_layout_width_half_falls_back_when_list_would_be_too_narrow2629–2651 ↗
fn side_layout_width_half_falls_back_when_list_would_be_too_narrow()

作用:测试左右并排会把列表挤得太窄时,会自动退回上下布局。

数据流:创建最小右侧宽度较大的视图 → 用内容宽度 80 查询 side_layout_width → 检查返回 None。

调用关系:保护 MIN_LIST_WIDTH_FOR_SIDE 这条可用性规则。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, default, assert_eq!, new_view, vec!)。

tests::stacked_side_content_is_used_when_side_by_side_does_not_fit2654–2689 ↗
fn stacked_side_content_is_used_when_side_by_side_does_not_fit()

作用:测试不能左右并排时,会使用专门的堆叠版侧边内容。

数据流:创建宽屏内容标记 W 和窄屏堆叠内容标记 N → 用较窄宽度渲染 → 检查出现 N 而不出现 W。

调用关系:验证 stacked_side_content 和 render 的窄屏分支。

调用图:调用 1 个内部函数(new);外部调用 6 个(new, default, assert!, new_view, render_lines_with_width, vec!)。

tests::side_content_clearing_resets_symbols_and_style2692–2748 ↗
fn side_content_clearing_resets_symbols_and_style()

作用:测试绘制右侧内容前后会正确清理旧字符和样式,避免红色背景等脏数据残留。

数据流:先把缓冲区填满 X 和红背景 → 渲染带右侧内容的视图 → 检查边缘被清空、标记字符背景被重置。

调用关系:覆盖 clear_to_terminal_bg 和 force_bg_to_terminal_bg 在 render 中的实际效果。

调用图:调用 1 个内部函数(new);外部调用 9 个(new, empty, default, new, default, assert!, assert_eq!, new_view, vec!)。

tests::side_content_clearing_handles_non_zero_buffer_origin2751–2789 ↗
fn side_content_clearing_handles_non_zero_buffer_origin()

作用:测试缓冲区起点不是 0 时,清理右侧内容区域也不会算错坐标。

数据流:创建 y 起点为 20 的缓冲区并填红色背景 → 渲染视图 → 检查右上角单元被清成空格和默认背景。

调用关系:保护 clear_to_terminal_bg 对非零区域坐标的裁剪逻辑。

调用图:调用 1 个内部函数(new);外部调用 8 个(new, empty, default, new, default, assert_eq!, new_view, vec!)。

tui/src/bottom_pane/multi_select_picker.rs源码 ↗
domain_logic用户打开底部多选弹窗后,在键盘输入处理和界面重绘期间活跃

这个文件像一个终端版的“带搜索的复选框清单”。它先定义每一项长什么样,比如名字、说明、是否已勾选、能不能移动;再定义 MultiSelectPicker 这个真正显示和响应键盘的控件。用户打字时,它会用模糊搜索(不必完全输入,只要大致匹配)筛出项目;按空格会切换勾选;按回车会把所有已勾选项目的 id 交给外部;按 Esc 或 Ctrl-C 会取消。它还支持预览行和变化回调,也就是每次勾选或移动后,可以通知外面的程序更新别的地方。渲染时,它会把标题、搜索框、列表、底部快捷键提示拼成一个弹窗。没有它,类似“选择多个功能开关/栏目/配置项”的操作就会分散在各处,既难用也容易出错。

函数细节41
MultiSelectItem::default134–143 ↗
fn default() -> Self

作用:生成一个空白的可选项目,给测试或构造项目时当默认底子用。这样调用者只需要改自己关心的字段,不用每次把所有字段都写一遍。

数据流:进去没有额外输入 → 它创建一个 id 和名字为空、没有说明、未勾选、默认可排序、没有分隔线的项目 → 出来一个 MultiSelectItem 默认值。

调用关系:测试里的 tests::item 会借它快速造项目;MultiSelectPicker::build_rows 也会用默认行结构的类似思路补齐显示行的其他字段。

调用图:外部调用 1 个(new)。

MultiSelectPicker::builder217–223 ↗
fn builder(
        title: String,
        subtitle: Option<String>,
        app_event_tx: AppEventSender,
    ) -> MultiSelectPickerBuilder

作用:给外部一个创建多选弹窗的入口。它不直接造最终弹窗,而是先返回一个“建造器”,让调用者一步步填项目、回调和选项。

数据流:进去标题、副标题和应用事件发送器 → 它把这些交给 MultiSelectPickerBuilder::new → 出来一个可以继续链式配置的 MultiSelectPickerBuilder。

调用关系:测试辅助函数和测试用例会从这里开始创建 picker;真实代码也会用它作为配置弹窗的起点,然后再调用 builder 的 items、on_confirm、build 等方法。

调用图:调用 1 个内部函数(new);被 4 处调用(page_and_jump_navigation_use_list_keymap, test_picker, new, new)。

MultiSelectPicker::apply_filter230–272 ↗
fn apply_filter(&mut self)

作用:根据当前搜索文字重新筛选列表,并尽量保住用户原来选中的那一项。它是搜索功能的核心,保证界面显示的项目和输入框内容同步。

数据流:进去的是对象里的 search_query、全部 items、当前选中位置和滚动位置 → 它把搜索词去掉首尾空格,空搜索就显示全部;有搜索就用 match_item 做模糊匹配并按分数排序;之后修正选中项和滚动范围 → 出来更新后的 filtered_indices 和 ScrollState。

调用关系:用户输入字符或退格时,MultiSelectPicker::handle_key_event 会调用它;移动项目后 MultiSelectPicker::move_selected_item 也会调用它,让内部顺序和显示顺序重新对齐。

调用图:调用 3 个内部函数(match_item, clamp_selection, ensure_visible);被 2 处调用(handle_key_event, move_selected_item);外部调用 2 个(max_visible_rows, new)。

MultiSelectPicker::visible_len275–277 ↗
fn visible_len(&self) -> usize

作用:告诉其他代码当前过滤后有多少项目可见。导航时需要这个数字,才能知道上下移动和翻页的边界在哪里。

数据流:进去读取 filtered_indices → 它取这个列表的长度 → 出来一个可见项目数量。

调用关系:上下移动、翻页、跳到顶部和底部这些函数都会先问它列表现在有多长,再交给滚动状态去移动。

调用图:被 6 处调用(jump_bottom, jump_top, move_down, move_up, page_down, page_up)。

MultiSelectPicker::max_visible_rows280–282 ↗
fn max_visible_rows(len: usize) -> usize

作用:算出列表最多显示多少行,避免弹窗因为项目太多而撑满屏幕。即使没有匹配项,也至少保留一行用来显示空状态。

数据流:进去一个项目数量 len → 它拿 len 和全局上限 MAX_POPUP_ROWS 比较,并保证最少按 1 行算 → 出来一个本次列表区域该显示的最大行数。

调用关系:过滤、上下移动、翻页和跳转都会用这个上限来保持滚动合理;渲染高度也围绕这个思路计算。

MultiSelectPicker::rows_width285–287 ↗
fn rows_width(total_width: u16) -> u16

作用:计算列表内容实际能用的宽度。因为弹窗边框或留白会占掉左右空间,所以不能直接用总宽度。

数据流:进去整个区域宽度 → 它安全地减去 2,宽度不够时不会变成负数 → 出来列表行可使用的宽度。

调用关系:MultiSelectPicker::render 在画列表时调用它,用来决定 render_rows_single_line 能写多宽。

MultiSelectPicker::rows_height290–296 ↗
fn rows_height(&self, rows: &BuiltRows) -> u16

作用:计算列表区域要占几行。它会根据实际要画的行数决定高度,但不超过弹窗允许的最大行数。

数据流:进去 BuiltRows,也就是已经准备好的显示行 → 它看 rows 里有多少行,把结果限制在 1 到 MAX_POPUP_ROWS 之间,再转成界面需要的高度数字 → 出来一个 u16 高度。

调用关系:MultiSelectPicker::desired_height 用它估算整个控件高度;MultiSelectPicker::render 用它切分实际绘制区域。

调用图:被 2 处调用(desired_height, render)。

MultiSelectPicker::build_rows302–345 ↗
fn build_rows(&self) -> BuiltRows

作用:把内部项目变成真正能画在屏幕上的一行行文字。它会加上光标符号、勾选框、截断过长名字,还会插入分隔线。

数据流:进去读取 filtered_indices、items 和当前选中位置 → 它逐个找到可见项目,生成类似“› [x] 名字”的显示行,必要时附上说明和分隔线,同时把选中位置从“项目序号”转换成“显示行序号” → 出来 BuiltRows,里面有显示行和对应的滚动状态。

调用关系:MultiSelectPicker::desired_height 和 MultiSelectPicker::render 都会调用它;它是数据状态到屏幕文字之间的转换层。

调用图:调用 1 个内部函数(truncate_text);被 2 处调用(desired_height, render);外部调用 4 个(default, new, with_capacity, format!)。

MultiSelectPicker::move_up348–353 ↗
fn move_up(&mut self)

作用:把列表光标往上一项移动,到顶时会绕到最后一项。这样用户可以用键盘快速浏览选项。

数据流:进去读取当前可见项目数和滚动状态 → 它让 ScrollState 上移并自动绕回,然后保证选中项仍在可见窗口里 → 出来更新后的选中位置和滚动位置。

调用关系:MultiSelectPicker::handle_key_event 在收到“上移”快捷键时调用它;实际移动细节交给 ScrollState。

调用图:调用 3 个内部函数(visible_len, ensure_visible, move_up_wrap);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

MultiSelectPicker::move_down356–361 ↗
fn move_down(&mut self)

作用:把列表光标往下一项移动,到底时会绕到第一项。它让键盘浏览列表更顺手。

数据流:进去读取当前可见项目数和滚动状态 → 它让 ScrollState 下移并自动绕回,然后调整滚动窗口确保光标看得见 → 出来更新后的选中位置和滚动位置。

调用关系:MultiSelectPicker::handle_key_event 在收到“下移”快捷键时调用它;滚动和边界处理交给 ScrollState。

调用图:调用 3 个内部函数(visible_len, ensure_visible, move_down_wrap);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

MultiSelectPicker::page_up363–367 ↗
fn page_up(&mut self)

作用:让光标向上翻一页,而不是只移动一项。项目很多时,这比一下一下按方向键快得多。

数据流:进去当前可见项目数和每页最大可见行数 → 它让 ScrollState 按页往上移动,并限制在列表范围内 → 出来新的选中位置和滚动位置。

调用关系:MultiSelectPicker::handle_key_event 在匹配到 page_up 快捷键时调用它;具体页移算法由 ScrollState 完成。

调用图:调用 2 个内部函数(visible_len, page_up_clamped);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

MultiSelectPicker::page_down369–373 ↗
fn page_down(&mut self)

作用:让光标向下翻一页。它服务于长列表,让用户不用逐项移动。

数据流:进去当前可见项目数和每页最大可见行数 → 它让 ScrollState 按页往下移动,并保证不越界 → 出来新的选中位置和滚动位置。

调用关系:MultiSelectPicker::handle_key_event 在匹配到 page_down 快捷键时调用它;它把实际滚动规则交给 ScrollState。

调用图:调用 2 个内部函数(visible_len, page_down_clamped);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

MultiSelectPicker::jump_top375–379 ↗
fn jump_top(&mut self)

作用:把光标直接跳到列表顶部。用户想回到第一个匹配项时会用到它。

数据流:进去当前可见项目数和最大可见行数 → 它让 ScrollState 把选中项和滚动窗口移到开头 → 出来顶部位置的状态。

调用关系:MultiSelectPicker::handle_key_event 在收到“跳到顶部”的快捷键时调用它;具体位置修正由 ScrollState 处理。

调用图:调用 2 个内部函数(visible_len, jump_top);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

MultiSelectPicker::jump_bottom381–385 ↗
fn jump_bottom(&mut self)

作用:把光标直接跳到列表底部。用户想快速到最后一个匹配项时会用到它。

数据流:进去当前可见项目数和最大可见行数 → 它让 ScrollState 把选中项移到末尾,并把滚动窗口调到能看见末尾的位置 → 出来底部位置的状态。

调用关系:MultiSelectPicker::handle_key_event 在收到“跳到底部”的快捷键时调用它;滚动细节由 ScrollState 完成。

调用图:调用 2 个内部函数(visible_len, jump_bottom);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

MultiSelectPicker::toggle_selected390–406 ↗
fn toggle_selected(&mut self)

作用:切换当前光标所在项目的勾选状态。按空格时,它把未选变已选,或把已选变未选。

数据流:进去读取当前选中可见位置、filtered_indices 和 items → 它找到真实项目,把 enabled 取反,然后刷新预览行;如果设置了变化回调,就把完整项目列表发给回调 → 出来更新后的 items,可能还触发外部事件。

调用关系:MultiSelectPicker::handle_key_event 收到空格键时调用它;它内部调用 MultiSelectPicker::update_preview_line,并可能通知 on_change 回调。

调用图:调用 1 个内部函数(update_preview_line);被 1 处调用(handle_key_event)。

MultiSelectPicker::confirm_selection412–427 ↗
fn confirm_selection(&mut self)

作用:确认当前选择并关闭弹窗。它会把所有已勾选项目的 id 收集起来交给外部处理。

数据流:进去读取 complete 状态、items 和 on_confirm 回调 → 如果已经结束就什么也不做;否则标记 complete=true,筛出 enabled=true 的项目 id → 出来一组选中 id,并通过回调传给外部。

调用关系:MultiSelectPicker::handle_key_event 在收到确认键时调用它;确认后的后续动作由调用者提供的 on_confirm 决定。

调用图:被 1 处调用(handle_key_event)。

MultiSelectPicker::move_selected_item436–491 ↗
fn move_selected_item(&mut self, direction: Direction)

作用:把当前选中的项目在原列表里向上或向下挪一格。它只在允许排序、没有搜索过滤、并且两边项目都可排序时生效。

数据流:进去方向、当前搜索词、选中位置、filtered_indices 和 items → 它先拒绝搜索状态或不可移动项目,再计算目标位置,交换两个项目;之后刷新预览、触发变化回调、重新应用过滤,并把光标放回被移动的项目上 → 出来新的项目顺序和选中位置。

调用关系:MultiSelectPicker::handle_key_event 在收到左右移动快捷键时调用它;它会调用 MultiSelectPicker::update_preview_line 和 MultiSelectPicker::apply_filter 来保持显示一致。

调用图:调用 2 个内部函数(apply_filter, update_preview_line);被 1 处调用(handle_key_event)。

MultiSelectPicker::update_preview_line496–501 ↗
fn update_preview_line(&mut self)

作用:重新生成底部预览行。预览行可以展示当前选择会带来的效果,比如“已选择 3 项”。

数据流:进去读取 preview_builder 回调和当前 items → 如果有回调,就把所有项目交给它生成一行文字;没有回调或回调返回空,就不显示预览 → 出来更新后的 preview_line。

调用关系:MultiSelectPicker::toggle_selected 和 MultiSelectPicker::move_selected_item 在状态变化后调用它;MultiSelectPicker::render 再把这个缓存好的预览画出来。

调用图:被 2 处调用(move_selected_item, toggle_selected)。

MultiSelectPicker::close506–514 ↗
fn close(&mut self)

作用:取消并关闭这个多选弹窗。它适合 Esc、Ctrl-C 或外部要求关闭时使用。

数据流:进去读取 complete 状态和 on_cancel 回调 → 如果已经关闭就不重复处理;否则标记 complete=true,并调用取消回调 → 出来弹窗进入完成状态,外部可能收到取消通知。

调用关系:MultiSelectPicker::handle_key_event 收到取消键会调用它;MultiSelectPicker::on_ctrl_c 也会调用它,所以 Ctrl-C 和普通取消走同一套关闭流程。

调用图:被 4 处调用(handle_key_event, on_ctrl_c, on_ctrl_c, on_ctrl_c)。

MultiSelectPicker::is_complete518–520 ↗
fn is_complete(&self) -> bool

作用:告诉外部这个弹窗是否已经结束。外层界面用它判断是否该把这个底部面板移走。

数据流:进去读取 complete 字段 → 它直接返回这个布尔值 → 出来 true 表示已确认或已取消,false 表示还在交互中。

调用关系:它实现 BottomPaneView 接口的一部分;外层底部面板框架会在生命周期检查时询问它。

MultiSelectPicker::on_ctrl_c522–525 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理 Ctrl-C。这里把 Ctrl-C 当作取消弹窗,而不是让程序崩掉或继续传递。

数据流:进去一个可变 picker → 它调用 close 关闭弹窗 → 出来 CancellationEvent::Handled,表示这个按键已经被处理。

调用关系:它是 BottomPaneView 接口的方法;底部面板框架收到 Ctrl-C 时会调用它,它再把实际关闭动作交给 MultiSelectPicker::close。

调用图:调用 1 个内部函数(close)。

MultiSelectPicker::handle_key_event527–589 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:这是多选弹窗的键盘总入口。所有按键,比如打字搜索、上下移动、空格勾选、回车确认、Esc 取消,都先到这里分流。

数据流:进去一个 KeyEvent 键盘事件,以及 picker 当前状态 → 它先判断普通字符是不是应该进入搜索框,再按快捷键匹配不同动作:移动光标、翻页、跳转、退格删搜索、空格切换、确认或取消;普通可打印字符会追加到 search_query 并重新过滤 → 出来更新后的搜索词、选中项、项目状态或完成状态。

调用关系:它实现 BottomPaneView 的输入处理;它会按情况调用 move_up、move_down、page_up、page_down、jump_top、jump_bottom、move_selected_item、toggle_selected、confirm_selection、close 和 apply_filter。

调用图:调用 12 个内部函数(apply_filter, close, confirm_selection, jump_bottom, jump_top, move_down, move_selected_item, move_up, page_down, page_up (+2 more));被 2 处调用(handle_key_event, handle_key_event)。

MultiSelectPicker::desired_height593–602 ↗
fn desired_height(&self, width: u16) -> u16

作用:估算这个弹窗希望占多高。外层布局需要这个数字来给它分配屏幕空间。

数据流:进去可用宽度 → 它先 build_rows 看列表会有多少显示行,再算标题高度、搜索框高度、列表高度、底部提示和预览高度 → 出来一个期望高度。

调用关系:它实现 Renderable 接口;外层渲染系统会在排版前调用它,内部依赖 MultiSelectPicker::build_rows 和 MultiSelectPicker::rows_height。

调用图:调用 2 个内部函数(build_rows, rows_height);被 2 处调用(desired_height, desired_height)。

MultiSelectPicker::render604–699 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把多选弹窗真正画到终端屏幕缓冲区里。它负责标题、搜索框、列表、预览和快捷键提示的完整布局。

数据流:进去屏幕区域 Rect 和可写的 Buffer → 它先切出内容区和底部区,画背景,再画标题、搜索提示、搜索内容、列表行;如果有预览就截断到合适宽度后画出来,最后画底部快捷键提示 → 出来 Buffer 被写入新的界面内容。

调用关系:它实现 Renderable 接口;外层 UI 重绘时调用它。它会调用 build_rows、rows_height、render_rows_single_line、truncate_line_with_ellipsis_if_overflow 等工具来完成绘制。

调用图:调用 6 个内部函数(build_rows, rows_height, render_rows_single_line, truncate_line_with_ellipsis_if_overflow, vh, user_message_style);被 2 处调用(render, render);外部调用 9 个(default, Fill, Length, Max, vertical, clone, from, rows_width, vec!)。

MultiSelectPickerBuilder::new731–745 ↗
fn new(title: String, subtitle: Option<String>, app_event_tx: AppEventSender) -> Self

作用:创建一个初始建造器,里面放好标题、副标题、事件发送器和默认键位。之后调用者可以继续补项目和回调。

数据流:进去标题、副标题和 AppEventSender → 它填入空项目列表、默认不启用排序、默认列表键位和空回调 → 出来一个 MultiSelectPickerBuilder。

调用关系:MultiSelectPicker::builder 会调用它;它是所有链式配置方法的起点。

调用图:调用 1 个内部函数(defaults);被 1 处调用(builder);外部调用 1 个(new)。

MultiSelectPickerBuilder::items748–751 ↗
fn items(mut self, items: Vec<MultiSelectItem>) -> Self

作用:给建造器设置可选择的项目列表。没有这一步,弹窗就没有东西可选。

数据流:进去建造器本身和一组 MultiSelectItem → 它把这组项目存进 builder → 出来更新后的 builder,方便继续链式调用。

调用关系:创建 picker 时通常在 builder 后立刻调用它;最后 MultiSelectPickerBuilder::build 会把这些项目搬进真正的 MultiSelectPicker。

MultiSelectPickerBuilder::enable_ordering756–759 ↗
fn enable_ordering(mut self) -> Self

作用:打开项目排序功能。开启后,用户可以用配置好的左右键把项目前后移动。

数据流:进去 builder → 它把 ordering_enabled 设为 true → 出来更新后的 builder。

调用关系:测试和需要排序的界面会调用它;MultiSelectPickerBuilder::build 会根据这个标志生成底部提示,MultiSelectPicker::handle_key_event 也会用它决定是否响应移动项目的快捷键。

MultiSelectPickerBuilder::list_keymap762–765 ↗
fn list_keymap(mut self, keymap: ListKeymap) -> Self

作用:替换这个弹窗使用的列表快捷键。这样不同环境可以自定义上下移动、翻页、确认和取消的按键。

数据流:进去 builder 和一个 ListKeymap → 它把 builder 里的 keymap 换成传入版本 → 出来更新后的 builder。

调用关系:测试 page_and_jump_navigation_use_list_keymap 会用它验证自定义键位;build 后,MultiSelectPicker::handle_key_event 会按这套键位解释按键。

MultiSelectPickerBuilder::on_preview771–777 ↗
fn on_preview(mut self, callback: F) -> Self

作用:设置一个生成预览行的函数。外部可以用它把当前选择实时变成一行提示文字。

数据流:进去 builder 和一个回调函数 → 它把回调装箱保存到 preview_builder → 出来更新后的 builder。

调用关系:MultiSelectPickerBuilder::build 会把这个回调交给 picker,并初始化一次预览;之后 toggle_selected 或 move_selected_item 会触发更新。

调用图:外部调用 1 个(new)。

MultiSelectPickerBuilder::on_change783–789 ↗
fn on_change(mut self, callback: F) -> Self

作用:设置“项目发生变化时”的回调。无论是勾选状态变化,还是顺序变化,都可以通知外部。

数据流:进去 builder 和一个接收 items 与事件发送器的回调 → 它把回调保存起来 → 出来更新后的 builder。

调用关系:MultiSelectPicker::toggle_selected 和 MultiSelectPicker::move_selected_item 会在变化后调用这个回调;build 负责把它放进 picker。

调用图:外部调用 1 个(new)。

MultiSelectPickerBuilder::on_confirm794–800 ↗
fn on_confirm(mut self, callback: F) -> Self

作用:设置用户按确认键后的处理函数。外部通常在这里保存选择结果或触发下一步操作。

数据流:进去 builder 和一个接收已选 id 列表的回调 → 它保存这个回调 → 出来更新后的 builder。

调用关系:MultiSelectPickerBuilder::build 把它交给 picker;MultiSelectPicker::confirm_selection 在回车确认时调用它。

调用图:外部调用 1 个(new)。

MultiSelectPickerBuilder::on_cancel803–809 ↗
fn on_cancel(mut self, callback: F) -> Self

作用:设置用户取消弹窗时的处理函数。外部可以在这里恢复状态或发出取消事件。

数据流:进去 builder 和一个接收事件发送器的回调 → 它保存这个回调 → 出来更新后的 builder。

调用关系:MultiSelectPickerBuilder::build 把它交给 picker;MultiSelectPicker::close 在 Esc 或 Ctrl-C 时调用它。

调用图:外部调用 1 个(new)。

MultiSelectPickerBuilder::build815–876 ↗
fn build(self) -> MultiSelectPicker

作用:把前面一步步配置好的内容组装成真正可用的 MultiSelectPicker。它是建造器流程的最后一步。

数据流:进去完整 builder,包括标题、项目、键位、回调和排序开关 → 它先做标题区域,再生成底部快捷键说明,然后创建 MultiSelectPicker;最后应用初始过滤并生成初始预览 → 出来一个已经准备好显示和接收按键的 picker。

调用关系:外部链式配置结束后调用它;它会使用 primary_binding 生成友好的按键提示,并调用 picker 的 apply_filter 和 update_preview_line 做初始化。

调用图:调用 3 个内部函数(new, primary_binding, new);外部调用 5 个(new, from, new, new, vec!)。

match_item895–909 ↗
fn match_item(
    filter: &str,
    display_name: &str,
    name: &str,
) -> Option<(Option<Vec<usize>>, i32)>

作用:判断一个项目是否匹配搜索词,并给出匹配分数。它让用户不必完整输入名字,也能搜到想要的项目。

数据流:进去搜索词 filter、显示名 display_name 和备用名字 name → 它先对显示名做 fuzzy_match;如果没匹配且备用名字不同,再试备用名字;匹配成功就返回高亮位置和分数,不匹配就返回 None → 出来一个可用于筛选和排序的结果。

调用关系:MultiSelectPicker::apply_filter 会对每个项目调用它;它底层依赖 codex_utils_fuzzy_match::fuzzy_match 完成真正的模糊匹配。

调用图:被 1 处调用(apply_filter);外部调用 1 个(fuzzy_match)。

tests::test_picker918–928 ↗
fn test_picker(items: Vec<MultiSelectItem>) -> MultiSelectPicker

作用:测试用的快捷工厂,快速造一个带默认标题、事件通道和排序功能的 picker。这样每个测试不用重复写一堆初始化代码。

数据流:进去一组测试项目 → 它创建一个测试用事件通道,调用 MultiSelectPicker::builder,塞入项目并开启排序,最后 build → 出来一个可在测试里直接操作的 MultiSelectPicker。

调用关系:多个测试函数都会调用它;它把测试重点从“怎么创建 picker”转移到“picker 行为是否正确”。

调用图:调用 2 个内部函数(new, builder)。

tests::item930–938 ↗
fn item(id: &str, orderable: bool, section_break_after: bool) -> MultiSelectItem

作用:测试用的小工具,用最少参数创建一个 MultiSelectItem。它让测试数据看起来更短、更容易读。

数据流:进去 id、是否可排序、是否在后面加分隔线 → 它用这些字段覆盖默认项目,其余字段沿用 MultiSelectItem::default → 出来一个测试项目。

调用关系:测试函数通过它批量创建列表项;它间接依赖 MultiSelectItem::default 提供默认字段。

调用图:外部调用 1 个(default)。

tests::non_orderable_items_cannot_move_or_be_crossed941–976 ↗
fn non_orderable_items_cannot_move_or_be_crossed()

作用:验证不可排序的项目不能被移动,也不能被其他项目跨过去。这样分组标题或固定项不会被用户误排乱。

数据流:进去由测试构造的三个项目 → 它先尝试移动不可排序项,再选中可排序项尝试跨过不可排序项;每次都检查最终顺序 → 出来断言通过表示顺序保护生效。

调用关系:它使用 tests::test_picker 创建 picker,并直接调用移动相关方法;目标是覆盖 MultiSelectPicker::move_selected_item 的边界规则。

调用图:外部调用 3 个(assert_eq!, test_picker, vec!)。

tests::horizontal_list_keys_reorder_orderable_items979–1008 ↗
fn horizontal_list_keys_reorder_orderable_items()

作用:验证配置里的左右移动快捷键真的能调整项目顺序。这里用 Ctrl+L 和 Ctrl+H 模拟向后、向前移动。

数据流:进去两个可排序项目组成的 picker → 它发送一个向右移动的键盘事件,检查顺序交换;再发送向左移动事件,检查顺序恢复 → 出来断言通过表示按键到排序动作的链路正常。

调用关系:它通过 MultiSelectPicker::handle_key_event 间接测试 move_selected_item;也验证默认 keymap 中水平移动键能被识别。

调用图:外部调用 5 个(Char, new, assert_eq!, test_picker, vec!)。

tests::section_break_after_item_renders_separator_row1011–1033 ↗
fn section_break_after_item_renders_separator_row()

作用:验证带分隔标记的项目后面会显示一条分割线。这样界面可以把固定区和普通区清楚隔开。

数据流:进去两个项目,其中第一个要求后面加分隔线 → 它构造 picker 并调用 build_rows → 出来检查显示行依次是第一项、分隔线、第二项,同时选中行仍然正确。

调用关系:它主要测试 MultiSelectPicker::build_rows 对 section_break_after 的处理。

调用图:外部调用 3 个(assert_eq!, test_picker, vec!)。

tests::searchable_plain_j_updates_query_instead_of_navigating1036–1051 ↗
fn searchable_plain_j_updates_query_instead_of_navigating()

作用:验证普通字母 j 会进入搜索框,而不是被当成向下移动。这样用户搜索包含 j 的内容时不会被快捷键抢走输入。

数据流:进去包含 alpha 和 jupiter 的 picker → 它发送一个无修饰键的 j → 出来 search_query 变成“j”,过滤结果只剩 jupiter,选中位置仍在可见列表第一项。

调用关系:它通过 MultiSelectPicker::handle_key_event 测试普通文本输入优先于导航别名的规则。

调用图:外部调用 5 个(Char, new, assert_eq!, test_picker, vec!)。

tests::page_and_jump_navigation_use_list_keymap1054–1094 ↗
fn page_and_jump_navigation_use_list_keymap()

作用:验证翻页和跳转使用的是传入的自定义键位,而不是写死的按键。这样用户配置键位后,弹窗行为才会一致。

数据流:进去一个自定义 ListKeymap 和 12 个项目 → 它先确认未绑定的 PageDown 不起作用,再依次发送自定义的 Ctrl+D、Ctrl+U、Ctrl+E、Ctrl+A,检查选中位置到达预期页、顶部或底部 → 出来断言通过表示自定义键位生效。

调用关系:它调用 MultiSelectPicker::builder 和 MultiSelectPickerBuilder::list_keymap 创建 picker,并通过 MultiSelectPicker::handle_key_event 覆盖 page_up、page_down、jump_top、jump_bottom 的调用路径。

调用图:调用 3 个内部函数(new, builder, defaults);外部调用 5 个(Char, from, new, assert_eq!, vec!)。

tui/src/bottom_pane/status_line_setup.rs源码 ↗
domain_logic用户打开状态栏设置弹窗时活跃;按键交互、预览、确认或取消期间使用

这个文件把底部状态栏的可选内容整理成一份菜单,比如模型名、当前目录、Git 分支、额度、线程标题等。它先定义 StatusLineItem,意思是“状态栏里可以放的一种信息”,并给每项配上给人看的说明和预览用的对应项。然后 StatusLineSetupView 把这些项交给一个多选列表组件 MultiSelectPicker(一个可勾选、可排序的弹窗列表)。打开设置时,它会把已有配置排在前面并勾上,没选的放后面;还有一个特殊开关“Use theme colors”,控制状态栏是否使用主题颜色。用户移动、勾选、排序时,预览会按当前选择实时生成。确认后,它发出 StatusLineSetup 事件,把新顺序和颜色开关交给应用保存;取消或 Ctrl+C 则发取消事件或关闭窗口。文件底部的测试保证旧配置名字仍能识别,预览也真的使用运行时数据。

函数细节23
StatusLineItem::description146–192 ↗
fn description(self) -> &'static str

作用:给某个状态栏项目返回一句给用户看的解释。这样设置弹窗里不只是显示一个短名字,还能告诉用户“勾上它会看到什么”。

数据流:进去的是一个 StatusLineItem,也就是某个可选状态栏项目 → 函数按项目种类挑出对应说明文字 → 出来的是一段固定英文说明,不改动任何状态。

调用关系:它主要被 StatusLineSetupView::status_line_select_item 使用;创建选择列表时,每个项目都要先拿到这句说明,再显示在多选弹窗里。

调用图:被 1 处调用(status_line_select_item)。

StatusLineItem::preview_item194–222 ↗
fn preview_item(self) -> StatusSurfacePreviewItem

作用:把“配置里的一项”转换成“预览系统认识的一项”。这相当于把菜单选项翻译成预览栏能显示的素材编号。

数据流:进去的是一个 StatusLineItem → 函数逐项匹配,把它映射到 StatusSurfacePreviewItem → 出来的是预览组件用来取示例值或真实值的项目类型。

调用关系:它被 StatusLineSetupView::status_line_select_item 用来生成更贴近实际情况的名称和说明,尤其是额度限制这类会随运行数据变化的项目。

调用图:被 1 处调用(status_line_select_item)。

StatusLineSetupView::new250–337 ↗
fn new(
        status_line_items: Option<&[String]>,
        use_theme_colors: bool,
        preview_data: StatusSurfacePreviewData,
        app_event_tx: AppEventSender,
        list_keymap: ListKey

作用:创建完整的状态栏设置弹窗。外部想打开“配置状态栏”界面时,就调用它来准备列表、预览、确认和取消行为。

数据流:进去的是当前已配置的项目顺序、是否使用主题颜色、预览数据、事件发送器和列表按键规则 → 它先放入“Use theme colors”开关,再把已有配置中能识别的项目按原顺序勾上并去重,最后补上其它未选项目 → 出来的是一个 StatusLineSetupView,里面包着已经配置好的 MultiSelectPicker;确认时会发送新配置事件,取消时会发送取消事件。

调用关系:应用在 open_status_line_setup 之类的打开设置流程中调用它;测试 setup_view_snapshot_uses_runtime_preview_values 也会调用它检查画面。它把具体列表项制作交给 StatusLineSetupView::status_line_select_item,把显示和按键交互交给 MultiSelectPicker。

调用图:调用 1 个内部函数(builder);被 2 处调用(setup_view_snapshot_uses_runtime_preview_values, open_status_line_setup);外部调用 4 个(new, status_line_select_item, iter, vec!)。

StatusLineSetupView::status_line_select_item340–363 ↗
fn status_line_select_item(
        item: StatusLineItem,
        enabled: bool,
        preview_data: &StatusSurfacePreviewData,
    ) -> MultiSelectItem

作用:把一个状态栏项目包装成多选列表里的一行。它决定这一行叫什么、说明写什么、是否默认勾选、能不能排序。

数据流:进去的是一个 StatusLineItem、是否启用、以及预览数据 → 它取项目的标准名字和说明;如果是使用额度项目,还会用预览数据生成更贴近当前额度名称和说明 → 出来的是 MultiSelectItem,也就是弹窗列表能直接显示和操作的一行。

调用关系:它服务于 StatusLineSetupView::new;新建弹窗时,每个普通状态栏项目都要经过它变成列表项。它会调用 StatusLineItem::description 和 StatusLineItem::preview_item,并在需要时请预览数据补充额度相关文字。

调用图:调用 4 个内部函数(description, preview_item, rate_limit_item_description, rate_limit_item_name);外部调用 1 个(to_string)。

StatusLineSetupView::handle_key_event367–369 ↗
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent)

作用:处理用户在这个设置弹窗里的按键,比如上下移动、勾选、调整顺序、确认或取消。

数据流:进去的是一次键盘事件 → 它不自己判断每个键,而是直接转交给内部的 MultiSelectPicker → 结果是列表状态可能变化,比如选中项移动、某项被勾选、顺序改变,或弹窗被标记完成。

调用关系:底部面板收到键盘输入时会调用它;它只是一个转接点,把状态栏设置视图接入通用多选列表组件。

调用图:调用 1 个内部函数(handle_key_event)。

StatusLineSetupView::is_complete371–373 ↗
fn is_complete(&self) -> bool

作用:告诉外层界面这个设置弹窗是不是已经结束。外层需要这个信号来决定是否关闭底部面板。

数据流:进去的是当前视图自身 → 它读取内部 picker 的 complete 标记 → 出来的是 true 或 false,不改变任何东西。

调用关系:底部面板的运行流程会在按键处理后查看它;真正的完成状态由 MultiSelectPicker 在确认或取消时设置。

StatusLineSetupView::on_ctrl_c375–378 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理用户按 Ctrl+C 的情况。这里的意思不是退出整个程序,而是关闭这个设置弹窗。

数据流:进去的是当前可变视图 → 它调用内部 picker 的 close,让弹窗进入关闭状态 → 出来的是 CancellationEvent::Handled,告诉外层“这次 Ctrl+C 已经被我处理了”。

调用关系:当底部面板收到 Ctrl+C 时会调用它;它把关闭动作交给 MultiSelectPicker,并阻止 Ctrl+C 继续被当成更大的退出命令处理。

调用图:调用 1 个内部函数(close)。

StatusLineSetupView::render382–384 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把状态栏设置弹窗画到终端屏幕缓冲区里。简单说,就是负责让用户看见这个窗口。

数据流:进去的是要绘制的屏幕区域和一块可写的 Buffer(屏幕内容草稿)→ 它把绘制工作交给内部 MultiSelectPicker → 出来时 Buffer 里已经填好了列表、说明和预览等内容。

调用关系:界面刷新时会调用它;测试里的 tests::render_lines 也调用它,把画面转成文字快照来比较。

调用图:调用 1 个内部函数(render);被 1 处调用(render_lines)。

StatusLineSetupView::desired_height386–388 ↗
fn desired_height(&self, width: u16) -> u16

作用:计算这个设置弹窗在给定宽度下想占多高。外层布局需要它来安排底部面板大小。

数据流:进去的是可用宽度 → 它询问内部 MultiSelectPicker 需要的高度 → 出来的是一个行数,不改变界面状态。

调用关系:布局阶段会调用它;tests::render_lines 先用它算高度,再创建 Buffer 并调用 render。

调用图:调用 1 个内部函数(desired_height);被 1 处调用(render_lines)。

tests::context_used_accepts_context_usage_legacy_id405–415 ↗
fn context_used_accepts_context_usage_legacy_id()

作用:测试“context-used”这个新名字能用,同时旧名字“context-usage”也还能被识别。这样老配置升级后不会突然失效。

数据流:进去的是几段固定字符串和枚举值 → 测试把枚举转成字符串、再把字符串解析回枚举 → 如果结果和预期一样就通过,不产生运行时输出。

调用关系:它由测试运行器调用;它保护 StatusLineItem 的字符串兼容规则,避免以后改名时弄坏旧用户配置。

调用图:外部调用 1 个(assert_eq!)。

tests::context_remaining_is_selectable_id418–427 ↗
fn context_remaining_is_selectable_id()

作用:测试“context-remaining”这个配置名字能被正确识别。它保证“剩余上下文比例”这一项真的能从配置中选出来。

数据流:进去的是字符串“context-remaining”和对应枚举 → 测试解析字符串并检查枚举转回字符串的结果 → 成功说明配置名和显示名一致。

调用关系:它由测试运行器调用;重点守住 StatusLineItem 的解析和输出规则。

调用图:外部调用 1 个(assert_eq!)。

tests::project_name_is_canonical_and_accepts_legacy_ids429–443 ↗
fn project_name_is_canonical_and_accepts_legacy_ids()

作用:测试项目名这一项现在的标准名字是“project-name”,同时旧名字“project”和“project-root”仍然可用。

数据流:进去的是多个项目相关字符串 → 测试分别解析它们,并检查都指向同一个 ProjectRoot 项 → 如果全部匹配就通过。

调用关系:它由测试运行器调用;它防止配置命名变更影响老用户。

调用图:外部调用 1 个(assert_eq!)。

tests::model_is_canonical_and_accepts_model_name_legacy_id446–456 ↗
fn model_is_canonical_and_accepts_model_name_legacy_id()

作用:测试模型名这一项现在用“model”作为标准名字,同时老的“model-name”仍能读取。

数据流:进去的是“model”和“model-name” → 测试把它们解析成 StatusLineItem::ModelName,并确认枚举输出的标准字符串是“model” → 没有额外副作用。

调用关系:它由测试运行器调用;它保护模型名配置项的新旧名称兼容。

调用图:外部调用 1 个(assert_eq!)。

tests::reasoning_is_selectable_id459–465 ↗
fn reasoning_is_selectable_id()

作用:测试“reasoning”这个项目 ID 可以正常解析。也就是确保用户能单独选择显示推理级别。

数据流:进去的是字符串“reasoning”和对应枚举 → 测试解析和转字符串是否都正确 → 结果只体现在测试是否通过。

调用关系:它由测试运行器调用;它覆盖 StatusLineItem::Reasoning 的基础配置名。

调用图:外部调用 1 个(assert_eq!)。

tests::run_state_is_canonical_and_accepts_status_legacy_id468–478 ↗
fn run_state_is_canonical_and_accepts_status_legacy_id()

作用:测试运行状态这一项现在叫“run-state”,但旧配置里的“status”也还能用。

数据流:进去的是“run-state”和“status” → 测试确认它们都能解析成 StatusLineItem::Status,并且标准输出名是“run-state” → 通过说明兼容正常。

调用关系:它由测试运行器调用;它防止“状态”这类常用项目因为改名而丢失。

调用图:外部调用 1 个(assert_eq!)。

tests::git_summary_items_are_selectable_ids481–490 ↗
fn git_summary_items_are_selectable_ids()

作用:测试 Git 摘要相关的两个项目 ID 能被识别:拉取请求编号和分支变更统计。

数据流:进去的是“pull-request-number”和“branch-changes” → 测试解析成对应 StatusLineItem → 成功说明这两个选项能从配置或界面选择中恢复。

调用关系:它由测试运行器调用;它覆盖 Git 状态栏项目的字符串解析规则。

调用图:外部调用 1 个(assert_eq!)。

tests::parse_status_line_items_accepts_title_only_variants493–502 ↗
fn parse_status_line_items_accepts_title_only_variants()

作用:测试一些只靠名字出现的状态栏项目也能批量解析,比如运行状态和任务进度。

数据流:进去的是字符串数组“run-state”“task-progress” → 测试把它们逐个解析并收集成列表 → 出来和预期枚举列表比较,匹配就通过。

调用关系:它由测试运行器调用;它模拟读取一组配置项时的解析过程。

调用图:外部调用 1 个(assert_eq!)。

tests::preview_uses_runtime_values505–546 ↗
fn preview_uses_runtime_values()

作用:测试预览会优先使用当前运行时的真实值,而不是只显示死板占位文字。

数据流:进去的是一份预览数据,例如模型名 gpt-5、目录 /repo,以及两个已启用列表项 → 测试生成状态栏预览文字 → 出来应是“gpt-5 · /repo”。

调用关系:它由测试运行器调用;它验证 StatusSurfacePreviewData 和状态栏项目映射能配合工作。

调用图:调用 1 个内部函数(from_iter);外部调用 2 个(new, assert_eq!)。

tests::preview_uses_placeholders_when_runtime_values_are_missing549–584 ↗
fn preview_uses_placeholders_when_runtime_values_are_missing()

作用:测试某项没有真实运行数据时,预览仍会显示一个合理的示例值。这样设置界面不会因为缺数据而空白难懂。

数据流:进去的是只包含模型名的预览数据,以及模型名和 Git 分支两个启用项 → 生成预览时,模型名用真实值,Git 分支用占位示例 → 出来应是“gpt-5 · feat/awesome-feature”。

调用关系:它由测试运行器调用;它保证预览功能在信息不完整时仍然可读。

调用图:调用 1 个内部函数(from_iter);外部调用 2 个(new, assert_eq!)。

tests::preview_includes_thread_title587–628 ↗
fn preview_includes_thread_title()

作用:测试线程标题能出现在状态栏预览里。线程标题就是当前会话的人类可读名字。

数据流:进去的是模型名和线程标题的预览数据,以及两个启用项 → 测试生成预览文字 → 出来应包含“gpt-5 · Roadmap cleanup”。

调用关系:它由测试运行器调用;它覆盖 ThreadTitle 这一项从配置项目到预览项目的映射。

调用图:调用 1 个内部函数(from_iter);外部调用 2 个(new, assert_eq!)。

tests::setup_view_snapshot_uses_runtime_preview_values631–663 ↗
fn setup_view_snapshot_uses_runtime_preview_values()

作用:测试整个设置弹窗画出来的样子,并确认预览区域使用了传入的运行时数据。快照测试就像给界面拍一张标准照片,以后改动时对照。

数据流:进去的是模拟事件通道、已选状态栏项目、主题颜色开关、预览数据和默认按键设置 → 它创建 StatusLineSetupView,再把界面渲染成文本 → 输出交给快照断言比较,确保画面没有意外变化。

调用关系:它由测试运行器调用;它会调用 StatusLineSetupView::new,并借助 tests::render_lines 把界面内容提取出来。

调用图:调用 4 个内部函数(new, new, from_iter, defaults);外部调用 1 个(assert_snapshot!)。

tests::render_lines665–686 ↗
fn render_lines(view: &StatusLineSetupView, width: u16) -> String

作用:把一个设置视图渲染成普通字符串,方便测试比较。它是测试用的小工具,不是用户界面运行时的核心逻辑。

数据流:进去的是 StatusLineSetupView 和宽度 → 它先问视图需要多高,再创建一块空 Buffer,调用 render 画进去,最后逐格读取字符并拼成多行文本 → 出来的是整块界面的字符串表示。

调用关系:它被 tests::setup_view_snapshot_uses_runtime_preview_values 调用;内部会使用 StatusLineSetupView::desired_height 和 StatusLineSetupView::render。

调用图:调用 2 个内部函数(desired_height, render);外部调用 2 个(empty, new)。

tests::line_text688–695 ↗
fn line_text(line: Option<Line<'static>>) -> Option<String>

作用:把一行带样式的文本提取成纯文字。测试只关心显示了哪些字,不关心颜色或样式时会用它。

数据流:进去的是一个可能为空的 Line;Line 是 ratatui 里“一行终端文本”,里面可分成多个带样式片段 → 它把所有片段的文字内容连起来 → 出来的是 Option<String>,如果原来没有行就还是 None。

调用关系:它被多个预览测试用来检查状态栏预览文字;它把带样式的界面数据简化成容易比较的普通字符串。

tui/src/bottom_pane/title_setup.rs源码 ↗
orchestration用户打开终端标题配置弹窗时活跃;在按键、预览、确认、取消期间持续参与

这个文件像一个“标题内容点菜器”。终端标题可以显示项目名、当前目录、运行状态、模型名、Git 分支、用量限制等很多小块信息。这里先用 TerminalTitleItem 列出所有可选小块,并给每个小块准备说明文字、预览用的数据来源和拼接时的分隔符。preview_line_for_title_items 会把用户当前选择的项目拼成一行预览标题;如果选了 activity,也就是工作中的活动提示,它会走特殊格式,因为这个提示和普通字段不太一样。TerminalTitleSetupView 则把这些小块交给 MultiSelectPicker(一个可多选、可排序的选择器),用户按键时它转交给选择器处理,确认、取消或改动时发出 AppEvent,让外层程序保存设置或更新预览。文件末尾的测试保证界面能画出来,也保证配置里的旧名字还能被识别。

函数细节24
TerminalTitleItem::description92–132 ↗
fn description(self) -> &'static str

作用:给某个终端标题项目返回一句给人看的说明。比如告诉用户“这个选项会显示 Git 分支”或“这个选项会显示当前模型”。

数据流:进去的是一个 TerminalTitleItem,也就是某个可选标题小块 → 函数按小块种类查表 → 出来一段固定英文说明文字,不改动任何状态。

调用关系:TerminalTitleSetupView::title_select_item 在生成选择列表时会调用它,把这段说明放到每个选项下面,方便用户知道自己勾的是什么。

调用图:被 1 处调用(title_select_item)。

TerminalTitleItem::preview_item134–162 ↗
fn preview_item(self) -> Option<StatusSurfacePreviewItem>

作用:把“标题里可选的项目”转换成“预览数据里能取值的项目”。简单说,就是告诉预览器:这个选项该从哪儿拿示例文字。

数据流:进去的是一个标题项目 → 函数判断它有没有对应的预览字段 → 出来 Some(预览字段) 或 None;像 Spinner 这种活动提示没有普通预览字段,所以返回 None。

调用关系:TerminalTitleSetupView::title_select_item 用它判断是否需要特殊显示限额名称;preview_line_for_title_items 也依赖它从 StatusSurfacePreviewData 里取预览值。

调用图:被 1 处调用(title_select_item)。

TerminalTitleItem::separator_from_previous169–179 ↗
fn separator_from_previous(self, previous: Option<Self>) -> &'static str

作用:决定两个标题小块之间用什么符号隔开。普通项目用“ | ”,但活动提示 Spinner 前后只用空格,让标题看起来更自然。

数据流:进去的是当前项目和前一个项目(可能没有)→ 函数判断当前或前一个是不是 Spinner → 出来一个分隔字符串,比如空字符串、空格或“ | ”。

调用关系:它是标题拼接时的小规则。preview_line_for_title_items 在把多个项目连成一行时会用到同样的直觉,保证预览标题读起来像真实标题。

preview_line_for_title_items182–221 ↗
fn preview_line_for_title_items(
    items: &[TerminalTitleItem],
    preview_data: &StatusSurfacePreviewData,
) -> Option<Line<'static>>

作用:根据用户当前勾选的标题项目,生成一行可以显示在界面里的预览文字。这样用户不用保存就能先看到终端标题大概长什么样。

数据流:进去的是一串 TerminalTitleItem 和一份 StatusSurfacePreviewData 预览数据 → 如果包含 Spinner,就调用 build_action_required_title_text 生成带“需要操作”风格的标题;否则逐个项目取预览值、加分隔符、拼成字符串 → 出来 Some(Line) 表示有预览可画,或者 None 表示没有可显示内容。

调用关系:TerminalTitleSetupView::new 给 MultiSelectPicker 设置 on_preview 回调时会间接用到它。选择器发现勾选变化后,就让它把当前选择变成预览行。它会借助外部的 build_action_required_title_text 处理活动提示的特殊标题格式。

调用图:外部调用 5 个(from, new, build_action_required_title_text, contains, iter)。

parse_terminal_title_items223–234 ↗
fn parse_terminal_title_items(ids: impl Iterator<Item = T>) -> Option<Vec<TerminalTitleItem>>

作用:把配置或界面里的字符串编号,转换成程序内部认识的标题项目列表。它采用“要么全成功,要么全失败”的规则,避免保存半对半错的标题设置。

数据流:进去的是一批字符串,比如“project-name”“activity” → 函数逐个解析成 TerminalTitleItem → 如果全部合法,出来 Some(Vec<TerminalTitleItem>),并保留原来的顺序;只要有一个不合法,就出来 None。

调用关系:TerminalTitleSetupView::new 里的预览、改动和确认回调都依赖这个转换,确保发出去的 AppEvent 只包含可靠的项目列表。测试 tests::parse_terminal_title_items_preserves_order、tests::parse_terminal_title_items_rejects_invalid_ids 和 tests::parse_terminal_title_items_accepts_kebab_case_variants 专门检查它。

调用图:被 3 处调用(parse_terminal_title_items_accepts_kebab_case_variants, parse_terminal_title_items_preserves_order, parse_terminal_title_items_rejects_invalid_ids);外部调用 1 个(map)。

TerminalTitleSetupView::new248–316 ↗
fn new(
        title_items: Option<&[String]>,
        preview_data: StatusSurfacePreviewData,
        app_event_tx: AppEventSender,
        list_keymap: ListKeymap,
    ) -> Self

作用:创建整个“配置终端标题”的弹窗视图。它把已有配置、所有可选项目、预览数据、按键设置和事件发送器组装成一个可交互的多选排序界面。

数据流:进去的是当前已配置的标题项目、预览数据、事件发送器和列表按键方案 → 它先解析已有配置,去重并保留顺序,再把未选项目补到后面;然后用 MultiSelectPicker::builder 建出选择器,并挂上预览、变化、确认、取消这些回调 → 出来一个 TerminalTitleSetupView,里面包着准备好的 picker。

调用关系:当外层 open_terminal_title_setup 要打开终端标题设置时会调用它。测试 tests::renders_title_setup_popup 也调用它来检查弹窗长相。它把真正的列表交互交给 MultiSelectPicker,把用户动作转成 AppEvent 发给外层应用。

调用图:调用 1 个内部函数(builder);被 2 处调用(renders_title_setup_popup, open_terminal_title_setup);外部调用 1 个(iter)。

TerminalTitleSetupView::title_select_item318–344 ↗
fn title_select_item(
        item: TerminalTitleItem,
        enabled: bool,
        preview_data: &StatusSurfacePreviewData,
    ) -> MultiSelectItem

作用:把一个标题项目变成选择器里的一行选项。也就是准备好这一行的编号、名字、说明、是否已勾选,以及是否能排序。

数据流:进去的是一个 TerminalTitleItem、是否启用、以及预览数据 → 它拿到项目的默认名字和说明;如果是五小时或每周限额这类项目,会用预览数据生成更贴近实际的名称和说明 → 出来一个 MultiSelectItem,供选择器展示和操作。

调用关系:TerminalTitleSetupView::new 在构造选项列表时反复调用它。它会调用 TerminalTitleItem::description、TerminalTitleItem::preview_item,以及预览数据里的 rate_limit_item_name 和 rate_limit_item_description。

调用图:调用 4 个内部函数(rate_limit_item_description, rate_limit_item_name, description, preview_item);外部调用 1 个(to_string)。

TerminalTitleSetupView::handle_key_event348–350 ↗
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent)

作用:处理用户在这个弹窗里按下的键,比如上下移动、勾选、取消勾选、调整顺序等。

数据流:进去的是一个键盘事件 → 它不自己解释按键,而是直接交给内部的 MultiSelectPicker → picker 根据按键改变当前选中行、勾选状态或完成状态。

调用关系:外层底部面板收到键盘事件时会通过 BottomPaneView 接口调用它。它的角色像前台接待,把具体按键工作转交给真正的选择器 handle_key_event。

调用图:调用 1 个内部函数(handle_key_event)。

TerminalTitleSetupView::is_complete352–354 ↗
fn is_complete(&self) -> bool

作用:告诉外层:这个弹窗是不是已经结束了。比如用户确认或取消后,外层就可以关掉它。

数据流:进去不需要额外输入,只读取内部 picker 的 complete 标记 → 出来 true 或 false → 不修改任何东西。

调用关系:外层底部面板会通过 BottomPaneView 接口询问它是否完成。它不做判断,只把 MultiSelectPicker 的状态原样报出去。

TerminalTitleSetupView::on_ctrl_c356–359 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理用户按 Ctrl+C 的情况。这里把它当作关闭当前设置弹窗,而不是让整个程序直接失控。

数据流:进去不需要额外数据 → 它调用 picker.close() 关闭选择器 → 出来 CancellationEvent::Handled,表示这个取消动作已经被这个弹窗处理了。

调用关系:外层捕获 Ctrl+C 时会通过 BottomPaneView 接口调用它。它把关闭动作交给 MultiSelectPicker 的 close,然后告诉外层不用再继续传播这个取消事件。

调用图:调用 1 个内部函数(close)。

TerminalTitleSetupView::render363–365 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把终端标题设置界面画到终端缓冲区里。缓冲区可以理解成一张还没真正显示到屏幕上的字符画布。

数据流:进去的是要绘制的区域 Rect 和可写的 Buffer → 它把这两个东西交给内部 picker.render → 结果是 Buffer 被填上列表、说明、勾选状态和预览等界面内容。

调用关系:界面刷新时会通过 Renderable 接口调用它。测试里的 tests::render_lines 也会调用它,把画出来的内容转成字符串做快照检查。

调用图:调用 1 个内部函数(render);被 1 处调用(render_lines)。

TerminalTitleSetupView::desired_height367–369 ↗
fn desired_height(&self, width: u16) -> u16

作用:计算这个弹窗在给定宽度下想要多高。外层排版需要这个数字,才能给它留出合适空间。

数据流:进去的是可用宽度 → 它把宽度交给内部 picker.desired_height → 出来一个高度数值,不改动状态。

调用关系:渲染布局前会通过 Renderable 接口调用它。tests::render_lines 也先用它算高度,再创建相同大小的 Buffer 来渲染。

调用图:调用 1 个内部函数(desired_height);被 1 处调用(render_lines)。

tests::render_lines379–400 ↗
fn render_lines(view: &TerminalTitleSetupView, width: u16) -> String

作用:这是测试用的小工具:把一个 TerminalTitleSetupView 真正画出来,再把画布内容转成多行字符串,方便和快照比较。

数据流:进去的是一个视图和宽度 → 它先问视图需要多高,创建对应的 Rect 和 Buffer,再调用 view.render 画进去;最后逐格读取字符 → 出来完整的文本画面字符串。

调用关系:tests::renders_title_setup_popup 调用它来生成弹窗截图文本。它会用到 TerminalTitleSetupView::desired_height 和 TerminalTitleSetupView::render。

调用图:调用 2 个内部函数(desired_height, render);外部调用 2 个(empty, new)。

tests::renders_title_setup_popup403–422 ↗
fn renders_title_setup_popup()

作用:检查终端标题设置弹窗能按预期画出来。它防止以后改代码时,不小心把界面布局或文字弄坏。

数据流:进去没有外部输入 → 测试创建一个假的事件通道、准备几个已选标题项、构造 TerminalTitleSetupView,再用 render_lines 生成画面 → 最后用快照断言确认画面和保存的标准结果一致。

调用关系:它调用 TerminalTitleSetupView::new 创建被测界面,也用默认预览数据和默认按键配置。快照断言 assert_snapshot! 负责比较画面。

调用图:调用 4 个内部函数(new, default, new, defaults);外部调用 1 个(assert_snapshot!)。

tests::parse_terminal_title_items_preserves_order425–438 ↗
fn parse_terminal_title_items_preserves_order()

作用:验证字符串解析后不会打乱用户设置的顺序。标题项目的顺序很重要,因为它直接决定终端标题从左到右怎么显示。

数据流:进去是一组按特定顺序排列的字符串 → 调用 parse_terminal_title_items → 断言出来的 TerminalTitleItem 列表顺序完全一致。

调用关系:它专门测试 parse_terminal_title_items,保证 TerminalTitleSetupView 保存或预览时不会偷偷重排用户选择。

调用图:调用 1 个内部函数(parse_terminal_title_items);外部调用 1 个(assert_eq!)。

tests::parse_terminal_title_items_rejects_invalid_ids441–444 ↗
fn parse_terminal_title_items_rejects_invalid_ids()

作用:验证只要有一个标题项目编号不认识,就拒绝整组结果。这样可以避免把错误配置的一部分误保存下来。

数据流:进去的是一组字符串,其中包含一个非法的“not-a-title-item” → 调用 parse_terminal_title_items → 断言结果是 None。

调用关系:它检查 parse_terminal_title_items 的“全有或全无”规则,保护 TerminalTitleSetupView 的预览和确认回调不会发出半解析的列表。

调用图:调用 1 个内部函数(parse_terminal_title_items);外部调用 1 个(assert_eq!)。

tests::activity_is_canonical_and_accepts_spinner_legacy_id447–457 ↗
fn activity_is_canonical_and_accepts_spinner_legacy_id()

作用:验证活动提示这个项目现在的标准名字是“activity”,同时旧配置里的“spinner”仍然能用。这样升级软件时不会弄坏老用户配置。

数据流:进去没有外部输入 → 测试把 TerminalTitleItem::Spinner 转成字符串,并分别解析“activity”和“spinner” → 断言它们都对应同一个 Spinner 项。

调用关系:它依赖枚举自动生成的字符串转换和解析能力,确保 TerminalTitleItem 的兼容名称没有被误删。

调用图:外部调用 1 个(assert_eq!)。

tests::project_name_is_canonical_and_accepts_project_legacy_id460–470 ↗
fn project_name_is_canonical_and_accepts_project_legacy_id()

作用:验证项目名这个项目的标准名字是“project-name”,但旧名字“project”也还能被识别。

数据流:进去没有外部输入 → 测试把 Project 转成字符串,再解析“project-name”和“project” → 断言都得到 TerminalTitleItem::Project。

调用关系:它保护配置兼容性。parse_terminal_title_items 以后解析用户配置时,也会受益于这个别名规则。

调用图:外部调用 1 个(assert_eq!)。

tests::thread_title_is_canonical_and_accepts_thread_legacy_id473–483 ↗
fn thread_title_is_canonical_and_accepts_thread_legacy_id()

作用:验证线程标题这个项目现在叫“thread-title”,但老配置里的“thread”仍然有效。

数据流:进去没有外部输入 → 测试字符串输出和两个输入名字的解析结果 → 断言它们都对应 TerminalTitleItem::Thread。

调用关系:它守住 TerminalTitleItem 的命名兼容性,避免老配置在升级后突然无法解析。

调用图:外部调用 1 个(assert_eq!)。

tests::model_is_canonical_and_accepts_model_name_legacy_id486–496 ↗
fn model_is_canonical_and_accepts_model_name_legacy_id()

作用:验证模型名项目的标准名字是“model”,同时接受旧名字“model-name”。

数据流:进去没有外部输入 → 测试 Model 的字符串形式,并解析“model”和“model-name” → 断言都得到 TerminalTitleItem::Model。

调用关系:它保证用户配置里的模型字段改名后仍可读取,间接支持 TerminalTitleSetupView::new 加载旧配置。

调用图:外部调用 1 个(assert_eq!)。

tests::run_state_is_canonical_and_accepts_status_legacy_id499–509 ↗
fn run_state_is_canonical_and_accepts_status_legacy_id()

作用:验证运行状态项目现在叫“run-state”,但旧名字“status”仍然能解析。

数据流:进去没有外部输入 → 测试 Status 的标准字符串,再解析“run-state”和“status” → 断言都得到 TerminalTitleItem::Status。

调用关系:它确保 TerminalTitleItem::Status 的配置编号既有新标准,也保留老入口,避免升级破坏用户标题设置。

调用图:外部调用 1 个(assert_eq!)。

tests::model_with_reasoning_has_distinct_id512–521 ↗
fn model_with_reasoning_has_distinct_id()

作用:验证“模型名加推理级别”这个项目有自己独立的编号“model-with-reasoning”。这能避免它和普通模型名混在一起。

数据流:进去没有外部输入 → 测试 ModelWithReasoning 转成字符串,再解析同名字符串 → 断言结果是 TerminalTitleItem::ModelWithReasoning。

调用关系:它保护 TerminalTitleItem 中新增选项的独立身份,防止配置解析时把它误当成 Model。

调用图:外部调用 1 个(assert_eq!)。

tests::reasoning_is_selectable_id524–530 ↗
fn reasoning_is_selectable_id()

作用:验证“reasoning”这个推理级别项目本身可以作为可选标题项使用。

数据流:进去没有外部输入 → 测试 Reasoning 的字符串形式,并解析“reasoning” → 断言得到 TerminalTitleItem::Reasoning。

调用关系:它确保 TerminalTitleSetupView 列出的推理级别选项,也能被配置文件和确认回调正确识别。

调用图:外部调用 1 个(assert_eq!)。

tests::parse_terminal_title_items_accepts_kebab_case_variants533–580 ↗
fn parse_terminal_title_items_accepts_kebab_case_variants()

作用:验证一大批标准的短横线命名都能被解析。短横线命名就是像“git-branch”“current-dir”这种适合写进配置文件的名字。

数据流:进去的是一串标准标题项目编号 → 调用 parse_terminal_title_items → 断言出来的列表包含对应的 TerminalTitleItem,并且顺序正确。

调用关系:它覆盖 parse_terminal_title_items 的主要正常路径,也间接检查 TerminalTitleItem 的字符串解析规则是否和配置文件约定一致。

调用图:调用 1 个内部函数(parse_terminal_title_items);外部调用 1 个(assert_eq!)。

tui/src/chatwidget/settings_popups.rs源码 ↗
orchestrationuser interaction / settings popup

聊天界面不只是显示对话,还要让用户临时改一些偏好,比如界面主题、Codex 的说话风格,或者试用还不稳定的新功能。这个文件就像一个“设置菜单服务员”:用户点到相关入口时,它准备菜单内容,判断当前能不能选,然后把菜单交给底部面板显示。比如选人格前,它会先确认启动已经完成、当前模型也支持人格;不满足就直接给用户提示,而不是打开一个没法用的菜单。选中人格时,它还会发出几个事件,通知后端本轮上下文要更新、界面状态要更新、选择也要保存。这里不真正画复杂界面,而是组装弹窗需要的标题、选项、说明和点击后的动作。

函数细节6
ChatWidget::open_theme_picker9–21 ↗
fn open_theme_picker(&mut self)

作用:打开主题选择器,让用户挑终端界面的配色。它会尽量根据当前配置、Codex 的主目录、终端宽度来准备一个合适的主题菜单。

数据流:进去的是当前 ChatWidget 里已有的配置和最近一次渲染宽度。它先尝试找到 Codex 的主目录,再把当前主题名、主目录、终端宽度交给 build_theme_picker_params 生成菜单参数。最后,它把这些参数交给 bottom_pane 显示,用户就能在底部弹出的选择界面里换主题。

调用关系:当用户触发主题选择入口时会用到它。它自己不负责列出每个主题的细节,而是把准备参数的工作交给 build_theme_picker_params,并借助 find_codex_home 找配置相关位置;最后由底部面板真正显示选择界面。

调用图:调用 1 个内部函数(build_theme_picker_params);外部调用 1 个(find_codex_home)。

ChatWidget::open_personality_popup23–39 ↗
fn open_personality_popup(&mut self)

作用:这是打开“人格/说话风格”选择弹窗前的守门员。它先检查现在是不是能选,不能选就给出清楚提示,能选才继续打开真正的菜单。

数据流:进去的是当前会话状态和当前模型信息。它先看启动配置是否完成;没完成就加一条普通提示消息。然后看当前模型是否支持人格;不支持就生成一条错误消息,告诉用户可以用 /model 换模型。两关都通过后,它调用 open_personality_popup_for_current_model 来创建并显示选择菜单。

调用关系:用户想改 Codex 说话风格时会先走这里。它不直接拼菜单,而是负责避免用户进入一个无效界面;真正的菜单构建工作交给 ChatWidget::open_personality_popup_for_current_model。

调用图:调用 1 个内部函数(open_personality_popup_for_current_model);外部调用 1 个(format!)。

ChatWidget::open_personality_popup_for_current_model41–91 ↗
fn open_personality_popup_for_current_model(&mut self)

作用:真正创建“人格选择”弹窗,列出可选风格,并给每个选项绑上选中后的动作。它让用户可以在 Friendly 和 Pragmatic 之间切换 Codex 的表达方式。

数据流:进去的是当前配置里的 personality、当前模型是否支持人格,以及发送应用事件的通道。它把每个人格变成一个 SelectionItem:包含名字、说明、是否为当前选中项、是否禁用,以及点击后要执行的动作。用户选中后,这些动作会发送事件:更新本轮上下文、更新界面中的人格状态、持久保存这次选择。最后它加上标题和底部提示,把整个选择视图交给 bottom_pane 显示。

调用关系:它由 ChatWidget::open_personality_popup 在检查通过后调用。它会用 ChatWidget::personality_label 和 ChatWidget::personality_description 提供用户能看懂的文字,并通过事件把选择结果传给应用的其他部分。

调用图:调用 1 个内部函数(new);被 1 处调用(open_personality_popup);外部调用 3 个(new, default, from)。

ChatWidget::open_experimental_popup93–114 ↗
fn open_experimental_popup(&mut self)

作用:打开实验功能菜单,让用户查看并切换那些还在试验阶段的功能。它只展示声明过可以出现在实验菜单里的功能。

数据流:进去的是全局功能清单 FEATURES、当前配置里的功能开关状态、应用事件发送器,以及底部面板的按键设置。它逐个检查功能说明,只有同时有菜单名称和描述的功能才会被放进列表;每一项都会带上当前是否已启用。然后它创建 ExperimentalFeaturesView,并让 bottom_pane 显示这个视图。

调用关系:当用户打开实验功能入口时会用到它。它负责把原始功能定义整理成界面可显示的项目,然后把后续显示和按键交互交给 ExperimentalFeaturesView。

调用图:调用 1 个内部函数(new);外部调用 1 个(new)。

ChatWidget::personality_label116–122 ↗
fn personality_label(personality: Personality) -> &'static str

作用:把程序内部的人格枚举值变成用户看得懂的短名字,比如 Friendly 或 Pragmatic。这样界面上不会直接暴露内部表示。

数据流:进去的是一个 Personality 值。它根据这个值做简单匹配,然后返回固定的英文标签字符串;不改动任何状态,也不产生副作用。

调用关系:它主要服务于 ChatWidget::open_personality_popup_for_current_model,用来给每个选择项填显示名称。它是一个小翻译表,把内部选项翻译成菜单文字。

ChatWidget::personality_description124–130 ↗
fn personality_description(personality: Personality) -> &'static str

作用:给每种人格提供一句简短说明,帮助用户明白选了以后 Codex 的说话方式会有什么变化。

数据流:进去的是一个 Personality 值。它根据不同人格返回对应说明文字,比如 Friendly 是更温暖协作,Pragmatic 是更简洁直接;它只返回文本,不修改界面或配置。

调用关系:它和 ChatWidget::personality_label 搭配使用,主要被人格弹窗构建过程拿来生成每个选项下面的解释文字,让用户不用猜这些名字是什么意思。

tui/src/bottom_pane/skills_toggle_view.rs源码 ↗
domain_logicmain loop / user interaction

这个文件解决一个很具体的问题:技能列表可能很多,用户需要在终端里快速找到某个技能,并决定启用还是停用。它像一个小型设置面板:上面有标题和说明,中间有搜索框,下面是带勾选状态的列表,底部提示哪些按键能操作。用户输入普通字符时,不会被当成快捷键,而是进入搜索框;方向键或配置好的列表快捷键用来移动选择;空格或确认键切换当前技能;取消键关闭弹窗。它还会把“某个技能被打开/关闭”这件事通过 AppEventSender 发出去,让应用其他部分真正保存变化。重要的一点是,搜索会过滤并排序结果,同时尽量保留原来选中的那一项,避免用户一输入就迷路。

函数细节24
SkillsToggleView::new60–84 ↗
fn new(
        items: Vec<SkillsToggleItem>,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

作用:创建一个新的技能开关弹窗,把技能列表、按键配置和发送应用事件的通道都装进去。有人打开“管理技能”界面时会用它。

数据流:进去的是技能项目列表、事件发送器和列表按键配置;它先准备标题说明、底部按键提示、空搜索词和滚动状态,然后立刻做一次过滤,让列表从一开始就有正确的可见项目;出来的是一个可以处理按键、绘制界面、发送开关事件的 SkillsToggleView。

调用关系:它通常由打开管理技能弹窗的流程调用,测试里也会用它造一个弹窗。创建过程中它会请 skills_toggle_hint_line 生成底部提示文字,并调用 apply_filter 完成初始列表准备。

调用图:调用 3 个内部函数(new, skills_toggle_hint_line, new);被 3 处调用(footer_hint_uses_list_keymap_accept_and_cancel, renders_basic_popup, open_manage_skills_popup);外部调用 4 个(new, from, new, new)。

SkillsToggleView::visible_len86–88 ↗
fn visible_len(&self) -> usize

作用:告诉其他操作:当前搜索结果里一共有多少项能看见。移动光标和翻页都需要这个数字,免得越界。

数据流:它读取 filtered_indices,也就是当前筛选后保留下来的项目编号列表;只取这个列表的长度;返回一个数字,不改动任何状态。

调用关系:move_up、move_down、page_up、page_down、jump_top 和 jump_bottom 在移动选择位置前都会先问它当前有多少可选项。

调用图:被 6 处调用(jump_bottom, jump_top, move_down, move_up, page_down, page_up)。

SkillsToggleView::max_visible_rows90–92 ↗
fn max_visible_rows(len: usize) -> usize

作用:算出列表区域最多显示几行。它防止弹窗因为项目太多而长到屏幕外。

数据流:进去的是当前结果数量;它把这个数量限制在 MAX_POPUP_ROWS 以内,并保证至少按 1 行来算;出来的是实际要显示的最大行数。

调用关系:过滤、移动、翻页和绘制高度计算都会用这个规则,保证所有地方对“最多显示多少行”的理解一致。

SkillsToggleView::apply_filter94–137 ↗
fn apply_filter(&mut self)

作用:根据搜索框里的文字重新筛选技能列表,并安排好当前选中项。用户输入或删除搜索内容时会用它。

数据流:它读取 search_query、全部 items、当前选中位置和旧的过滤结果;如果搜索词为空,就显示全部技能;如果不为空,就用 match_skill 判断哪些技能匹配,并按匹配分数和名字排序;最后更新 filtered_indices,尽量把原来选中的技能继续选中,再修正滚动位置,避免选中项跑出可见区域。

调用关系:handle_key_event 在用户打字或按退格时调用它。它把“搜索内容变化”转成“新的可见列表和选择位置”,并依赖 ScrollState 的 clamp_selection、ensure_visible 来保持界面稳定。

调用图:调用 3 个内部函数(clamp_selection, ensure_visible, match_skill);被 1 处调用(handle_key_event);外部调用 2 个(max_visible_rows, new)。

SkillsToggleView::build_rows139–158 ↗
fn build_rows(&self) -> Vec<GenericDisplayRow>

作用:把内部的技能数据变成可以直接画到屏幕上的一行行文字。它会加上选中箭头和启用标记。

数据流:它读取 filtered_indices、items 和当前 selected_idx;每个可见技能会被转换成 GenericDisplayRow,名字前面加上“›”表示当前选中,加上“[x]”或“[ ]”表示开关状态,并带上描述;输出是一组显示行,不修改原数据。

调用关系:desired_height 用它知道要为多少行预留高度,render 用它真正画列表。它也会调用 truncate_skill_name,避免名字太长撑坏界面。

调用图:被 2 处调用(desired_height, render)。

SkillsToggleView::move_up160–165 ↗
fn move_up(&mut self)

作用:把当前选择往上移动一项。到顶部时会按 ScrollState 的规则处理循环或边界。

数据流:它先读取当前可见项目数量;让 ScrollState 执行向上移动;再根据最多可见行数调整滚动窗口,保证新选中的项还在屏幕里;它不返回值,但会改变选择和滚动状态。

调用关系:handle_key_event 在用户按“上移”快捷键时调用它。它把具体边界处理交给 ScrollState 的 move_up_wrap 和 ensure_visible。

调用图:调用 3 个内部函数(ensure_visible, move_up_wrap, visible_len);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

SkillsToggleView::move_down167–172 ↗
fn move_down(&mut self)

作用:把当前选择往下移动一项。用户浏览技能列表时会反复触发它。

数据流:它读取当前可见项目数量;让 ScrollState 把选择往下挪;再调整滚动位置,让新选中的项目不会被藏在列表窗口外;结果是内部选择位置发生变化。

调用关系:handle_key_event 在用户按“下移”快捷键时调用它。它和 move_up 是一对,负责普通的逐项浏览。

调用图:调用 3 个内部函数(ensure_visible, move_down_wrap, visible_len);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

SkillsToggleView::page_up174–178 ↗
fn page_up(&mut self)

作用:向上翻一页,而不是只移动一行。列表很长时,用户可以更快跳过一段。

数据流:它读取当前可见数量,算出一页最多几行;然后让 ScrollState 把选择位置向上移动一页并限制在合法范围内;不返回值,只改变选择和滚动状态。

调用关系:handle_key_event 在用户按“上一页”快捷键时调用它。具体怎么不越界由 ScrollState 的 page_up_clamped 完成。

调用图:调用 2 个内部函数(page_up_clamped, visible_len);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

SkillsToggleView::page_down180–184 ↗
fn page_down(&mut self)

作用:向下翻一页,方便在长列表中快速浏览。

数据流:它读取当前可见数量,算出一页大小;交给 ScrollState 把选择往下翻一页,并保证不会超过列表末尾;最后更新内部选择和滚动状态。

调用关系:handle_key_event 在用户按“下一页”快捷键时调用它。它和 page_up 对应,都是大步移动。

调用图:调用 2 个内部函数(page_down_clamped, visible_len);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

SkillsToggleView::jump_top186–190 ↗
fn jump_top(&mut self)

作用:直接跳到当前搜索结果的第一项。用户想回到列表开头时会用它。

数据流:它读取当前可见数量和可显示行数;让 ScrollState 把选择和滚动位置调到顶部;不返回内容,只改变内部状态。

调用关系:handle_key_event 在用户按“跳到顶部”快捷键时调用它。具体位置计算交给 ScrollState 的 jump_top。

调用图:调用 2 个内部函数(jump_top, visible_len);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

SkillsToggleView::jump_bottom192–196 ↗
fn jump_bottom(&mut self)

作用:直接跳到当前搜索结果的最后一项。用户想快速到列表尾部时会用它。

数据流:它读取当前可见数量和可显示行数;让 ScrollState 把选择移到末尾,并把滚动窗口也调到能看见末尾;不返回值。

调用关系:handle_key_event 在用户按“跳到底部”快捷键时调用它。它和 jump_top 组成首尾跳转。

调用图:调用 2 个内部函数(jump_bottom, visible_len);被 1 处调用(handle_key_event);外部调用 1 个(max_visible_rows)。

SkillsToggleView::toggle_selected198–214 ↗
fn toggle_selected(&mut self)

作用:切换当前选中技能的启用状态。也就是把开着的关掉,把关着的打开。

数据流:它先看当前有没有选中项,再把可见列表里的编号换成原始 items 里的编号;找到对应技能后,把 enabled 取反;然后通过 app_event_tx 发出 SetSkillEnabled 事件,带上技能文件路径和新的开关状态。它会改动本地显示状态,也会通知应用保存变化。

调用关系:handle_key_event 在用户按空格或确认键时调用它。它不自己写文件,而是把“状态变了”这件事发给应用事件系统处理。

调用图:调用 1 个内部函数(send);被 1 处调用(handle_key_event)。

SkillsToggleView::close216–224 ↗
fn close(&mut self)

作用:关闭这个技能管理弹窗,并通知应用重新加载技能列表。这样用户刚改的开关能反映到主界面里。

数据流:它先检查 complete,避免重复关闭;如果还没关闭,就把 complete 设为 true,发送 ManageSkillsClosed 事件;接着请求 list_skills,并要求强制重新加载。它不返回值,但会改变完成状态并发出事件。

调用关系:on_ctrl_c 调用它来处理取消操作。关闭时它不仅告诉界面“我结束了”,还推动应用重新读取技能,避免旧状态继续显示。

调用图:调用 2 个内部函数(list_skills, send);被 1 处调用(on_ctrl_c);外部调用 1 个(new)。

SkillsToggleView::rows_width226–228 ↗
fn rows_width(total_width: u16) -> u16

作用:计算技能列表行应该占多宽。它会给边距留出一点空间。

数据流:进去的是弹窗总宽度;它安全地减去 2,宽度太小时不会变成负数;出来的是列表绘制宽度。

调用关系:render 在真正画列表前会用它算布局。它是一个小工具,确保列表不要贴着边框画。

SkillsToggleView::rows_height230–232 ↗
fn rows_height(&self, rows: &[GenericDisplayRow]) -> u16

作用:计算技能列表区域应该有多高。它既不能少于 1 行,也不能超过弹窗允许的最大行数。

数据流:进去的是已经构造好的显示行;它读取行数,把它限制在 1 到 MAX_POPUP_ROWS 之间,再转成界面布局需要的高度数字;返回高度,不改状态。

调用关系:desired_height 用它估算整个弹窗高度,render 用它分配列表区域。这样高度计算和实际绘制保持一致。

调用图:被 2 处调用(desired_height, render);外部调用 1 个(len)。

SkillsToggleView::handle_key_event236–288 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:处理用户在这个弹窗里按下的每一个键。它决定这个键是搜索、移动、翻页、切换,还是关闭。

数据流:进去的是一个 KeyEvent,也就是一次键盘输入;它先判断普通字符是否应该进入搜索框,避免把字母 j/k 这类可输入字符误当成移动键;然后按键盘配置分派到 move_up、move_down、page_up、page_down、jump_top、jump_bottom、toggle_selected、on_ctrl_c,或者修改 search_query 并重新 apply_filter;它不返回结果,但会改变搜索词、选择位置、开关状态或关闭状态。

调用关系:这是运行时最核心的入口之一,由底部面板的事件循环在收到键盘输入时调用。它像交通警察,把不同按键送去对应的小函数处理。

调用图:调用 10 个内部函数(apply_filter, jump_bottom, jump_top, move_down, move_up, on_ctrl_c, page_down, page_up, toggle_selected, is_plain_text_key_event)。

SkillsToggleView::is_complete290–292 ↗
fn is_complete(&self) -> bool

作用:告诉外层界面这个弹窗是否已经结束。外层需要知道什么时候可以把它移除。

数据流:它只读取 complete 这个布尔值;返回 true 表示已经关闭,false 表示还在使用;不改任何状态。

调用关系:它是 BottomPaneView 接口的一部分。外层底部面板会通过这个统一方法检查当前视图是否该退出。

SkillsToggleView::on_ctrl_c294–297 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理取消操作,比如用户按 Ctrl+C 或配置的取消键。这里的做法是关闭弹窗,并说明这个取消事件已经被处理。

数据流:它没有额外输入,只使用当前视图状态;调用 close 发送关闭和重新加载事件;最后返回 CancellationEvent::Handled,表示不用再让外层继续处理这次取消。

调用关系:handle_key_event 在检测到取消快捷键时调用它。它把真正的收尾工作交给 close。

调用图:调用 1 个内部函数(close);被 1 处调用(handle_key_event)。

SkillsToggleView::desired_height301–309 ↗
fn desired_height(&self, width: u16) -> u16

作用:告诉布局系统:这个弹窗理想情况下需要多高。这样外层才能给它安排合适的屏幕空间。

数据流:进去的是可用宽度;它先 build_rows 得到当前可显示的列表行,再算列表高度、标题高度、搜索框和底部提示所需空间;最后返回一个总高度。

调用关系:测试辅助函数 render_lines 会调用它来准备缓冲区。实际界面布局也依赖 Renderable 接口的这个方法来决定显示尺寸。

调用图:调用 2 个内部函数(build_rows, rows_height);被 1 处调用(render_lines)。

SkillsToggleView::render311–387 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把整个技能开关弹窗画到终端屏幕缓冲区里。它负责标题、搜索框、列表和底部按键提示的实际显示。

数据流:进去的是可绘制区域 area 和屏幕缓冲区 buf;它先检查宽高是否为 0,然后划分内容区和底部提示区,画背景样式,计算标题、搜索框和列表的位置;接着渲染标题、搜索占位文字或当前搜索词、技能行列表,最后画底部快捷键提示;结果是 buf 被填上要显示的字符和样式。

调用关系:外层界面每次刷新时会调用它。它会使用 build_rows、rows_height、rows_width 和 render_rows_single_line,把内部状态变成用户能看到的终端画面。

调用图:调用 5 个内部函数(render_rows_single_line, build_rows, rows_height, vh, user_message_style);被 1 处调用(render_lines);外部调用 9 个(default, Fill, Length, Max, vertical, clone, from, rows_width, vec!)。

skills_toggle_hint_line390–421 ↗
fn skills_toggle_hint_line(keymap: &ListKeymap) -> Line<'static>

作用:生成底部的按键提示文字,比如“按空格切换,按某个键关闭”。它会尊重用户配置的确认键和取消键。

数据流:进去的是 ListKeymap,也就是列表操作的按键配置;它固定把空格作为切换键之一,再查看确认键和取消键的主要绑定;如果确认键不是空格,就一起显示;如果有取消键,也显示关闭提示;出来的是一行可以直接渲染的提示文字。

调用关系:SkillsToggleView::new 创建弹窗时调用它。它依赖 key_hint 和 primary_binding 把按键配置转换成适合给人看的文本。

调用图:调用 2 个内部函数(plain, primary_binding);被 1 处调用(new);外部调用 3 个(Char, from, vec!)。

tests::render_lines433–454 ↗
fn render_lines(view: &SkillsToggleView, width: u16) -> String

作用:测试用的小工具:把一个 SkillsToggleView 画出来,并转成普通字符串,方便断言显示效果。

数据流:进去的是视图和宽度;它先问 desired_height 要多高,再创建一个空的终端缓冲区,调用 render 把界面画进去;然后逐格读取缓冲区字符,拼成多行文本;输出是一整个字符串。

调用关系:两个测试都会用它查看界面最终长什么样。它把复杂的终端缓冲区变成容易比较的文本。

调用图:调用 2 个内部函数(desired_height, render);外部调用 2 个(empty, new)。

tests::renders_basic_popup457–478 ↗
fn renders_basic_popup()

作用:测试基本弹窗能正常画出来。它用两个示例技能检查标题、搜索框、列表和开关状态是否符合预期。

数据流:它创建一个测试用事件通道,准备两个技能项目,一个启用一个停用;再用默认按键配置创建 SkillsToggleView;最后通过 render_lines 渲染,并用快照测试比较输出。它不改真实文件。

调用关系:这是渲染回归测试。以后如果有人改了界面外观,快照会提醒开发者确认变化是不是故意的。

调用图:调用 3 个内部函数(new, new, defaults);外部调用 2 个(assert_snapshot!, vec!)。

tui/src/bottom_pane/memories_settings_view.rs源码 ↗
domain_logic用户打开记忆设置弹窗后,在交互和渲染期间活跃

这个文件像一个小设置面板。它把“Use memories”“Generate memories”“Reset all memories”三项做成可上下移动的列表,并处理键盘操作、画面显示和保存动作。没有它,用户就很难在终端里直观地改记忆功能,只能去手动改配置文件,清空记忆也容易误操作。这里还特别做了二次确认:点“Reset all memories”后,不会马上删除,而是先进入确认页,让用户在“真的重置”和“返回”之间选。它通过 AppEventSender 发送事件,意思是把“用户想保存设置”或“用户想重置记忆”告诉应用主流程,真正写配置或删文件不在这个文件里做。整体上,它负责一个弹窗从打开、移动光标、勾选、确认、取消,到画出来的完整体验。

函数细节28
MemoriesSettingsView::new71–109 ↗
fn new(
        use_memories: bool,
        generate_memories: bool,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

作用:创建一个新的记忆设置弹窗,并把当前的两个开关状态放进去。外部打开记忆设置时会调用它。

数据流:进去的是当前是否使用记忆、是否生成记忆、用于发事件的通道,以及按键配置 → 它组装三行菜单、初始化滚动状态、准备说明链接和快捷键 → 出来的是一个可以显示和接收键盘操作的 MemoriesSettingsView。

调用关系:它由 open_memories_popup 在打开弹窗时调用。创建过程中会初始化内部列表和选择状态,之后弹窗的按键处理、保存、渲染都围绕这个对象进行。

调用图:调用 1 个内部函数(new);被 1 处调用(open_memories_popup);外部调用 2 个(from, vec!)。

MemoriesSettingsView::initialize_selection111–113 ↗
fn initialize_selection(&mut self)

作用:给菜单设置一开始选中的行。只要菜单不是空的,就默认选中第一项。

数据流:进去的是弹窗里已有的菜单项 → 它检查列表是否为空 → 如果有内容,就把当前选中位置设为第 0 行。

调用关系:它在 MemoriesSettingsView::new 创建弹窗时使用,让用户一打开面板就有明确的光标位置,不会出现不知道当前操作哪一项的情况。

MemoriesSettingsView::settings_header115–122 ↗
fn settings_header(&self) -> ColumnRenderable<'_>

作用:生成普通设置页顶部的标题和说明文字。它告诉用户这里是在设置记忆功能,并提示改动会保存到 config.toml。

数据流:进去的是当前视图本身 → 它创建一个可逐行显示的标题区域,放入粗体标题和灰色说明 → 出来的是给渲染器使用的顶部内容。

调用关系:render 画普通设置页时会用它,desired_height 估算弹窗需要多高时也会用它。它负责普通页面的“开场白”。

调用图:调用 1 个内部函数(new);被 2 处调用(desired_height, render);外部调用 1 个(from)。

MemoriesSettingsView::reset_confirmation_header124–132 ↗
fn reset_confirmation_header(&self) -> ColumnRenderable<'_>

作用:生成重置确认页顶部的标题和警告说明。它让用户知道接下来会清掉本地记忆文件和摘要。

数据流:进去的是当前视图本身 → 它创建确认页的标题区域,放入“Reset all memories?”和具体后果说明 → 出来的是确认页顶部要显示的内容。

调用关系:当用户选择重置后,render 会改用它来画确认页;desired_height 也会用它来计算确认页高度。

调用图:调用 1 个内部函数(new);被 2 处调用(desired_height, render);外部调用 1 个(from)。

MemoriesSettingsView::active_state134–136 ↗
fn active_state(&self) -> &ScrollState

作用:拿到当前真正应该使用的滚动和选中状态。普通设置页和重置确认页各有自己的选择状态,它负责选对那一个。

数据流:进去的是视图当前状态 → 如果正在重置确认页,就返回确认页的 ScrollState;否则返回普通列表的 ScrollState → 出来的是只读的当前活动状态。

调用关系:render 和 desired_height 会用它来知道当前选中哪一行、列表滚到哪里。它让同一套渲染逻辑可以同时适配普通页和确认页。

调用图:被 2 处调用(desired_height, render)。

MemoriesSettingsView::active_state_mut138–140 ↗
fn active_state_mut(&mut self) -> &mut ScrollState

作用:拿到当前活动页面的可修改滚动状态。用户按上下翻页时,需要改的就是这个状态。

数据流:进去的是视图当前状态 → 它判断现在是在确认页还是普通页 → 出来的是可修改的 ScrollState,后续移动函数会直接改它。

调用关系:move_up、move_down、page_up、page_down、jump_top、jump_bottom 都通过它改选中位置。它是键盘导航和内部状态之间的桥。

调用图:被 6 处调用(jump_bottom, jump_top, move_down, move_up, page_down, page_up)。

MemoriesSettingsView::visible_len142–148 ↗
fn visible_len(&self) -> usize

作用:告诉导航逻辑当前页面有几行可以选。普通设置页是三项,确认页固定是两项。

数据流:进去的是当前是否处在重置确认页的信息 → 它返回 2,或者返回普通菜单项数量 → 出来的是导航时用的行数。

调用关系:上下移动、翻页、跳到顶部和底部这些函数都会先问它有多少行,避免光标跑到不存在的地方。

调用图:被 6 处调用(jump_bottom, jump_top, move_down, move_up, page_down, page_up)。

MemoriesSettingsView::build_rows150–202 ↗
fn build_rows(&self) -> Vec<GenericDisplayRow>

作用:把内部菜单数据变成真正能显示在弹窗里的行。它会加上箭头、勾选框和每行说明。

数据流:进去的是当前菜单、当前选中位置,以及是否处在重置确认页 → 它为每一行拼出显示文字和说明文字;选中的行前面加“›”,开关项显示[x]或[ ] → 出来的是一组 GenericDisplayRow,交给绘制函数画出来。

调用关系:render 需要它来画列表,desired_height 需要它来估算列表占多少高度。它是“内部数据”变成“用户看得懂的文字列表”的转换点。

调用图:被 2 处调用(desired_height, render)。

MemoriesSettingsView::move_up204–212 ↗
fn move_up(&mut self)

作用:把当前选中项往上移动一格。到顶部再往上时会绕到末尾,像循环菜单一样。

数据流:进去的是当前活动页面的行数和选中状态 → 它取得可修改状态,向上移动,并确保新选中的行仍在可见范围里 → 出来的是更新后的选中位置和滚动位置。

调用关系:handle_key_event 在用户按“上移”按键时调用它。它会使用 visible_len 和 active_state_mut 完成实际移动。

调用图:调用 2 个内部函数(active_state_mut, visible_len);被 1 处调用(handle_key_event)。

MemoriesSettingsView::move_down214–222 ↗
fn move_down(&mut self)

作用:把当前选中项往下移动一格。到底部再往下时会绕回开头。

数据流:进去的是当前活动页面的行数和选中状态 → 它取得可修改状态,向下移动,并调整滚动范围让选中项看得见 → 出来的是新的选中位置和滚动位置。

调用关系:handle_key_event 在用户按“下移”按键时调用它。它和 move_up 是一对,负责最常见的列表导航。

调用图:调用 2 个内部函数(active_state_mut, visible_len);被 1 处调用(handle_key_event)。

MemoriesSettingsView::page_up224–228 ↗
fn page_up(&mut self)

作用:让选中项向上翻一页。适合列表较长时快速移动,虽然这里最多只显示有限几行。

数据流:进去的是当前行数和最多可见行数 → 它计算当前能显示多少行,并让活动状态向上翻页但不越界 → 出来的是更新后的选中位置和滚动位置。

调用关系:handle_key_event 在用户按翻页向上快捷键时调用它。它通过 active_state_mut 改当前页面的滚动状态。

调用图:调用 2 个内部函数(active_state_mut, visible_len);被 1 处调用(handle_key_event)。

MemoriesSettingsView::page_down230–234 ↗
fn page_down(&mut self)

作用:让选中项向下翻一页。用于比一格一格移动更快地浏览列表。

数据流:进去的是当前行数和最多可见行数 → 它计算可见范围,并让活动状态向下翻页但不超过列表末尾 → 出来的是新的选中和滚动状态。

调用关系:handle_key_event 在用户按翻页向下快捷键时调用它。它和 page_up 一起提供快速导航。

调用图:调用 2 个内部函数(active_state_mut, visible_len);被 1 处调用(handle_key_event)。

MemoriesSettingsView::jump_top236–240 ↗
fn jump_top(&mut self)

作用:把光标直接跳到列表第一项。用户想快速回到开头时会用到。

数据流:进去的是当前行数和可见行数 → 它修改当前活动状态,把选中位置设到顶部,并调整滚动 → 出来的是光标在第一行的状态。

调用关系:handle_key_event 在用户按“跳到顶部”快捷键时调用它。它不关心当前是普通页还是确认页,因为 active_state_mut 会选对状态。

调用图:调用 2 个内部函数(active_state_mut, visible_len);被 1 处调用(handle_key_event)。

MemoriesSettingsView::jump_bottom242–246 ↗
fn jump_bottom(&mut self)

作用:把光标直接跳到列表最后一项。用户想快速到末尾,比如选择重置项时会用到。

数据流:进去的是当前行数和可见行数 → 它修改当前活动状态,把选中位置设到最后一行,并调整滚动 → 出来的是光标在最后一行的状态。

调用关系:handle_key_event 在用户按“跳到底部”快捷键时调用它。它依赖 visible_len 判断最后一行在哪里。

调用图:调用 2 个内部函数(active_state_mut, visible_len);被 1 处调用(handle_key_event)。

MemoriesSettingsView::toggle_selected248–260 ↗
fn toggle_selected(&mut self)

作用:切换当前选中设置项的开关。比如把“使用记忆”从开变关,或从关变开。

数据流:进去的是当前是否在确认页、当前选中行和菜单项 → 如果在确认页就什么也不做;如果选中的是设置项,就把 enabled 取反 → 出来的是更新后的开关状态。

调用关系:handle_key_event 在用户按空格时调用它。它只负责临时改界面里的勾选状态,真正保存要等 save 发送事件。

调用图:被 1 处调用(handle_key_event)。

MemoriesSettingsView::rows_width262–264 ↗
fn rows_width(total_width: u16) -> u16

作用:计算列表行实际可用的宽度。它会给边距留出一点空间,避免文字贴着边框。

数据流:进去的是弹窗总宽度 → 它安全地减去 2,宽度太小时不会变成负数 → 出来的是列表文字区域宽度。

调用关系:渲染和高度估算时需要知道一行能放多少字,才能决定文字换行和列表高度。它是布局计算里的一个小工具。

MemoriesSettingsView::current_setting266–278 ↗
fn current_setting(&self, setting: MemoriesSetting) -> bool

作用:读取某个记忆设置当前是否打开。保存时需要用它把界面上的勾选结果取出来。

数据流:进去的是要查询的设置类型,比如 Use 或 Generate → 它在菜单项里找到对应设置,并读出 enabled 值 → 出来的是 true 或 false;找不到时返回 false。

调用关系:save 在发送 UpdateMemorySettings 事件前调用它。它把用户在界面上看到的勾选状态变成应用主流程能保存的布尔值。

调用图:被 1 处调用(save)。

MemoriesSettingsView::open_reset_confirmation280–284 ↗
fn open_reset_confirmation(&mut self)

作用:打开“是否真的重置记忆”的二次确认页。这样用户不会一按回车就误删记忆。

数据流:进去的是当前视图 → 它创建一个新的 ScrollState,并默认选中确认页第一行“Reset all memories” → 出来的是 reset_confirmation 被设置后的视图状态。

调用关系:save 在用户选中“Reset all memories”并确认时调用它。之后渲染和按键导航都会切换到确认页逻辑。

调用图:调用 1 个内部函数(new);被 1 处调用(save)。

MemoriesSettingsView::close_reset_confirmation286–289 ↗
fn close_reset_confirmation(&mut self)

作用:关闭重置确认页,回到普通记忆设置列表。返回后会把光标放回普通列表的最后一项,也就是重置入口附近。

数据流:进去的是当前确认页状态和普通菜单长度 → 它清空 reset_confirmation,并把普通列表选中项设为最后一行 → 出来的是回到普通设置页的状态。

调用关系:save 在用户选择“Go back”时会调用它,cancel 在确认页取消时也会调用它。它让二次确认页可以安全退出。

调用图:被 2 处调用(cancel, save)。

MemoriesSettingsView::handle_key_event301–318 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:处理用户在这个弹窗里按下的键。它把各种按键翻译成移动、切换、保存或取消动作。

数据流:进去的是一个 KeyEvent,也就是一次键盘输入 → 它按键盘映射判断这是上移、下移、翻页、跳转、空格、确认还是取消 → 出来的是视图内部状态被更新,或者触发保存/取消流程。

调用关系:这是 BottomPaneView 接口的一部分,弹窗运行时由外层界面把键盘事件交给它。它再把活儿分给 move_up、move_down、page_up、page_down、jump_top、jump_bottom、toggle_selected、save、cancel 等函数。

调用图:调用 9 个内部函数(cancel, jump_bottom, jump_top, move_down, move_up, page_down, page_up, save, toggle_selected)。

MemoriesSettingsView::is_complete320–322 ↗
fn is_complete(&self) -> bool

作用:告诉外层界面这个弹窗是不是已经结束了。结束后外层就可以把它关掉。

数据流:进去的是当前视图状态 → 它读取 complete 标记 → 出来的是 true 或 false。

调用关系:这是 BottomPaneView 接口的一部分。save 或 cancel 会把 complete 设为 true,外层流程再通过这个函数知道该收起弹窗。

MemoriesSettingsView::on_ctrl_c324–327 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理用户按 Ctrl+C 的情况。这里把 Ctrl+C 当作取消弹窗,而不是让程序直接崩掉或退出。

数据流:进去的是当前视图 → 它调用 cancel 执行取消逻辑 → 出来的是 CancellationEvent::Handled,表示这次取消已经被弹窗处理了。

调用关系:这是 BottomPaneView 接口的一部分。外层捕到 Ctrl+C 时会问当前视图怎么处理,它把工作交给 cancel。

调用图:调用 1 个内部函数(cancel)。

MemoriesSettingsView::save331–357 ↗
fn save(&mut self)

作用:执行“确认/保存”动作。它会根据当前页面和选中项,决定是保存开关、打开重置确认页,还是发送重置记忆事件。

数据流:进去的是当前页面状态、当前选中项和菜单里的开关值 → 如果在确认页且选中重置,就发送 ResetMemories 并标记完成;如果在确认页选返回,就关闭确认页;如果普通页选中重置入口,就打开确认页;否则发送 UpdateMemorySettings,带上两个开关值,并标记完成 → 出来的是事件被发给应用主流程,或页面状态改变。

调用关系:handle_key_event 在用户按确认键时调用它。它会用 current_setting 读取开关,用 open_reset_confirmation 或 close_reset_confirmation 切换确认页,用 app_event_tx.send 把真正要做的事交给应用其他部分。

调用图:调用 4 个内部函数(send, close_reset_confirmation, current_setting, open_reset_confirmation);被 1 处调用(handle_key_event);外部调用 1 个(unreachable!)。

MemoriesSettingsView::cancel359–365 ↗
fn cancel(&mut self)

作用:执行取消动作。普通页取消会关闭弹窗;确认页取消只会退回普通页,防止用户误以为已经关闭整个设置。

数据流:进去的是当前是否在重置确认页 → 如果在确认页,就关闭确认页;否则把 complete 设为 true → 出来的是回到设置页,或标记弹窗结束。

调用关系:handle_key_event 在用户按取消键时调用它,on_ctrl_c 也会调用它。它是所有取消路径的统一出口。

调用图:调用 1 个内部函数(close_reset_confirmation);被 2 处调用(handle_key_event, on_ctrl_c)。

MemoriesSettingsView::render369–434 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把整个记忆设置弹窗画到终端屏幕上。用户看到的标题、列表、说明链接和底部快捷键提示都由它安排。

数据流:进去的是可绘制区域 Rect 和屏幕缓冲区 Buffer → 它先检查区域是否为空,再划分内容区和底部提示区,选择普通标题或确认标题,构造列表行,计算高度,绘制背景、标题、列表、文档链接和快捷键提示 → 出来的是缓冲区被填上要显示的字符和样式。

调用关系:这是 Renderable 接口的一部分,终端界面刷新时会调用它。它会调用 settings_header、reset_confirmation_header、build_rows、active_state、footer_hint、measure_rows_height 和 render_rows,把数据变成最终画面。

调用图:调用 10 个内部函数(active_state, build_rows, footer_hint, reset_confirmation_header, settings_header, measure_rows_height, render_rows, vh, user_message_style, mark_url_hyperlink);外部调用 7 个(default, Fill, Length, Max, vertical, clone, rows_width)。

MemoriesSettingsView::desired_height436–459 ↗
fn desired_height(&self, width: u16) -> u16

作用:估算这个弹窗在给定宽度下需要多高。外层布局需要这个数字来安排屏幕空间。

数据流:进去的是可用宽度 → 它选择当前页面标题,构造列表行,计算列表换行后的高度,再加上标题、间距、文档链接和底部提示的高度 → 出来的是建议使用的高度。

调用关系:这是 Renderable 接口的一部分。外层布局在正式渲染前会用它决定给弹窗留多少行;它和 render 使用相似的计算,避免实际显示时高度对不上。

调用图:调用 5 个内部函数(active_state, build_rows, reset_confirmation_header, settings_header, measure_rows_height);外部调用 1 个(rows_width)。

memories_settings_hint_line462–470 ↗
fn memories_settings_hint_line() -> Line<'static>

作用:生成普通设置页底部的快捷键提示。它告诉用户空格可以切换,回车可以保存或选择。

数据流:进去没有额外参数 → 它把普通文字和按键样式片段拼成一行 → 出来的是一条 Line,用来显示在弹窗底部。

调用关系:footer_hint 在普通设置页会调用它。确认页不用这条提示,而是改用标准弹窗提示。

调用图:被 1 处调用(footer_hint);外部调用 2 个(from, vec!)。

tui/src/bottom_pane/hooks_browser_view.rs源码 ↗
domain_logic底部面板打开后到关闭前;在主界面事件循环中响应按键并渲染

hook 像是系统里的“自动触发器”:比如工具运行前、权限申请时、会话开始时,自动跑一段命令。这个文件把这些触发器做成一个可操作的底部弹窗。用户先看到按事件分类的总览表,每类显示装了几个、真正可运行几个、几个还需要审核;按回车后进入某个事件的 hook 列表,看每条 hook 的来源、命令、超时和信任状态。它还处理键盘操作:上下移动、翻页、进入详情、返回、关闭、空格/回车切换启用状态、按 t 信任。比较重要的是:管理员管理的 hook 不能被用户关闭;未信任或被修改过的 hook 即使标记为启用,也不会算作 active,必须先信任。文件底部还带了大量测试,用快照和事件检查来保证界面显示、颜色提示和按键行为不出错。

函数细节77
HooksBrowserView::new60–76 ↗
fn new(
        hooks: Vec<HookMetadata>,
        warnings: Vec<String>,
        errors: Vec<codex_app_server_protocol::HookErrorInfo>,
        app_event_tx: AppEventSender,
    ) -> Self

作用:这是测试用的快捷构造函数,用一组 hook、警告和错误直接做出一个浏览器视图。它方便测试不用准备完整的外部入口数据。

数据流:输入 hook 列表、警告、错误和事件发送器 → 把它们包成一个 HooksListEntry,并使用默认按键配置 → 输出一个排好序、能显示和响应操作的 HooksBrowserView。

调用关系:它主要被测试函数调用;内部把真正的创建工作交给 HooksBrowserView::from_entry,所以测试和正式入口走的是同一套初始化逻辑。

调用图:调用 1 个内部函数(defaults);被 18 处调用(assert_unmanaged_toggle_key, managed_hooks_count_as_active, renders_command_details_with_three_line_cap, renders_empty_handler_browser_message, renders_event_browser_with_issues, renders_event_browser_with_review_column_when_needed, renders_review_needed_handler, renders_scrolled_handler_window, renders_selected_managed_handler, renders_untrusted_enabled_handler_as_inactive (+8 more));外部调用 2 个(from_entry, new)。

HooksBrowserView::from_entry78–101 ↗
fn from_entry(
        mut entry: HooksListEntry,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

作用:这是正式创建 Hooks 浏览器视图的入口。它把服务器给来的 hook 清单整理好,并决定一开始选中哪一行。

数据流:输入一份 HooksListEntry、应用事件发送器和列表按键配置 → 按 display_order 给 hook 排序,初始化页面为事件总览页和滚动状态 → 输出可用于显示的 HooksBrowserView;如果有需要审核的事件,会默认选中它,否则选第一行。

调用关系:打开 hook 浏览器时由 open_hooks_browser 调用;它设置好后,后续按键由 handle_key_event 处理,显示由 render 处理。

调用图:调用 1 个内部函数(new);被 1 处调用(open_hooks_browser)。

HooksBrowserView::event_rows103–133 ↗
fn event_rows(&self) -> Vec<EventRow>

作用:生成事件总览表里的每一行。每行对应一种 hook 触发时机,并统计安装数、可用数和待审核数。

数据流:读取当前视图里的所有 hook → 遍历所有可能的 HookEventName,按事件名筛选并计数 → 输出 EventRow 列表,供表格显示。

调用关系:event_table_lines 会用它来画总览表;测试也通过它确认 managed hook 算 active、待审核 hook 不算 active。

调用图:被 1 处调用(event_table_lines);外部调用 1 个(iter)。

HooksBrowserView::handlers_for_event135–140 ↗
fn handlers_for_event(&self, event_name: HookEventName) -> impl Iterator<Item = &HookMetadata>

作用:找出属于某个事件的所有 hook。比如只看“工具执行前”会触发的那些 hook。

数据流:输入一个事件名 → 从当前 hook 列表中过滤 event_name 相同的项目 → 输出一个可逐个读取的 hook 迭代器,不修改数据。

调用关系:page_len、handler_row_lines、review_needed_count 都靠它取得某个事件下的 hook;它是事件页进入详情页后的基础筛选器。

调用图:被 3 处调用(handler_row_lines, page_len, review_needed_count)。

HooksBrowserView::selected_event142–147 ↗
fn selected_event(&self) -> Option<HookEventName>

作用:把当前选中的总览表行转换成具体的事件名。没有选中时就返回空。

数据流:读取滚动状态里的 selected_idx → 在所有事件名里找对应序号 → 输出对应 HookEventName,或者 None。

调用关系:open_selected_event 会用它决定按回车后进入哪个事件的 hook 列表。

调用图:被 1 处调用(open_selected_event)。

HooksBrowserView::selected_hook_index149–158 ↗
fn selected_hook_index(&self, event_name: HookEventName) -> Option<usize>

作用:把“当前页面里选中的第几条 hook”换算成原始 hook 列表里的真实下标。这样后面才能修改正确的 hook。

数据流:输入事件名,并读取当前选中行号 → 先过滤出该事件的 hook,再找到第 N 个 → 输出它在完整 entry.hooks 里的下标,找不到则返回 None。

调用关系:selected_hook、toggle_selected_hook、trust_selected_hook 都要先靠它定位 hook;它避免在筛选后的列表和原始列表之间改错对象。

调用图:被 3 处调用(selected_hook, toggle_selected_hook, trust_selected_hook)。

HooksBrowserView::selected_hook160–163 ↗
fn selected_hook(&self, event_name: HookEventName) -> Option<&HookMetadata>

作用:取得当前选中的 hook 的只读信息。用于显示详情或判断底部提示该说什么。

数据流:输入事件名 → 调用 selected_hook_index 找真实下标 → 从 hook 列表里读取对应 HookMetadata → 输出 hook 引用,或没有选中时输出 None。

调用关系:detail_lines 用它显示详情,render_footer 用它决定提示用户能不能切换、能不能信任。

调用图:调用 1 个内部函数(selected_hook_index);被 2 处调用(detail_lines, render_footer)。

HooksBrowserView::move_up165–169 ↗
fn move_up(&mut self)

作用:把选择光标向上移动一行,并处理滚动,让选中项保持可见。

数据流:读取当前页面长度和最多可见行数 → 调用 ScrollState 的向上循环移动和可见性修正 → 更新 selected_idx 和 scroll_top。

调用关系:handle_key_event 在用户按上移键时调用它;它把具体滚动细节交给 ScrollState。

调用图:调用 4 个内部函数(max_visible_rows, page_len, ensure_visible, move_up_wrap);被 1 处调用(handle_key_event)。

HooksBrowserView::move_down171–175 ↗
fn move_down(&mut self)

作用:把选择光标向下移动一行,并在需要时滚动列表。

数据流:读取当前页面长度和可见行数 → 调用 ScrollState 的向下循环移动 → 更新选择位置和滚动顶部。

调用关系:handle_key_event 在用户按下移键时调用它;它负责让事件页和 hook 列表页用同一套移动规则。

调用图:调用 4 个内部函数(max_visible_rows, page_len, ensure_visible, move_down_wrap);被 1 处调用(handle_key_event)。

HooksBrowserView::page_up177–180 ↗
fn page_up(&mut self)

作用:向上翻一页,让用户快速浏览长列表。

数据流:读取页面总长度和可见行数 → 让 ScrollState 按一页的距离向上移动,但不越界 → 更新当前选择和滚动位置。

调用关系:handle_key_event 在用户按翻页上键时调用它;实际边界处理由 ScrollState 完成。

调用图:调用 3 个内部函数(max_visible_rows, page_len, page_up_clamped);被 1 处调用(handle_key_event)。

HooksBrowserView::page_down182–185 ↗
fn page_down(&mut self)

作用:向下翻一页,用来快速跳过一屏内容。

数据流:读取页面总长度和可见行数 → 让 ScrollState 按一页的距离向下移动,并限制在合法范围内 → 更新界面状态。

调用关系:handle_key_event 在用户按翻页下键时调用它;它和 page_up 成对工作。

调用图:调用 3 个内部函数(max_visible_rows, page_len, page_down_clamped);被 1 处调用(handle_key_event)。

HooksBrowserView::jump_top187–190 ↗
fn jump_top(&mut self)

作用:直接跳到当前列表顶部。适合长列表里快速回到第一项。

数据流:读取当前页面长度和可见行数 → 调用 ScrollState 的跳顶方法 → 选中第一项并调整滚动。

调用关系:handle_key_event 在用户按“到顶部”的快捷键时调用它。

调用图:调用 3 个内部函数(max_visible_rows, page_len, jump_top);被 1 处调用(handle_key_event)。

HooksBrowserView::jump_bottom192–195 ↗
fn jump_bottom(&mut self)

作用:直接跳到当前列表底部。适合长列表里快速看最后一项。

数据流:读取当前页面长度和可见行数 → 调用 ScrollState 的跳底方法 → 选中最后一项并调整滚动。

调用关系:handle_key_event 在用户按“到底部”的快捷键时调用它。

调用图:调用 3 个内部函数(max_visible_rows, page_len, jump_bottom);被 1 处调用(handle_key_event)。

HooksBrowserView::page_len197–202 ↗
fn page_len(&self) -> usize

作用:告诉别的代码当前页面一共有多少行可选。事件总览页和某个事件的 hook 列表页算法不同。

数据流:读取当前 page → 如果是 Events,就数所有事件类型;如果是 Handlers,就数该事件下的 hook → 输出行数。

调用关系:移动、翻页、打开事件、计算可见行数都靠它知道边界;Handlers 页会进一步调用 handlers_for_event。

调用图:调用 1 个内部函数(handlers_for_event);被 8 处调用(jump_bottom, jump_top, max_visible_rows, move_down, move_up, open_selected_event, page_down, page_up);外部调用 1 个(iter)。

HooksBrowserView::max_visible_rows204–206 ↗
fn max_visible_rows(&self) -> usize

作用:计算列表最多能显示多少行,防止弹窗太高。即使没有内容,也至少按 1 行来处理布局。

数据流:读取当前页面长度 → 和 MAX_POPUP_ROWS 取较小值,并保证基础值不为 0 → 输出最大可见行数。

调用关系:所有移动和翻页函数都会用它,让滚动逻辑知道一屏有多大。

调用图:调用 1 个内部函数(page_len);被 6 处调用(jump_bottom, jump_top, move_down, move_up, page_down, page_up)。

HooksBrowserView::open_selected_event208–217 ↗
fn open_selected_event(&mut self)

作用:从事件总览页进入当前选中事件的 hook 列表页。

数据流:读取当前选中的事件 → 如果没有选中就什么也不做;如果有,就把 page 改成 Handlers(event),重置滚动状态,并默认选中第一条 hook → 修改视图状态。

调用关系:handle_key_event 在总览页按确认键时调用它;进入后 render 会显示 hook 列表和详情。

调用图:调用 3 个内部函数(page_len, selected_event, new);被 1 处调用(handle_key_event);外部调用 1 个(Handlers)。

HooksBrowserView::toggle_selected_hook219–238 ↗
fn toggle_selected_hook(&mut self, event_name: HookEventName)

作用:打开或关闭当前选中的普通 hook。管理员管理的 hook 和待审核的 hook 不能在这里切换。

数据流:输入事件名 → 找到当前选中的 hook → 如果是 managed 或需要审核就退出;否则把 enabled 取反,并发送 SetHookEnabled 应用事件 → 本地状态和外部持久状态都会被推动更新。

调用关系:handle_key_event 在 Handlers 页按回车或空格时调用它;真正保存启用状态的工作由收到 AppEvent 的上层逻辑完成。

调用图:调用 3 个内部函数(send, selected_hook_index, hook_needs_review);被 1 处调用(handle_key_event)。

HooksBrowserView::trust_selected_hook240–256 ↗
fn trust_selected_hook(&mut self, event_name: HookEventName)

作用:信任当前选中的、需要审核的 hook。信任后它才有资格运行。

数据流:输入事件名 → 找到选中的 hook → 如果它并不需要审核就退出;否则把 trust_status 改成 Trusted,并发送 TrustHook 事件,附带 key 和当前 hash → 本地显示立即变为可信,同时通知外部保存信任结果。

调用关系:handle_key_event 在 Handlers 页按 t 时调用它;它和 trust_all_hooks 分别处理单个和批量信任。

调用图:调用 3 个内部函数(send, selected_hook_index, hook_needs_review);被 1 处调用(handle_key_event)。

HooksBrowserView::trust_all_hooks258–274 ↗
fn trust_all_hooks(&mut self)

作用:一次性信任所有需要审核的 hook。适合总览页显示有多个待审核项时快速确认。

数据流:遍历当前所有 hook → 对每个需要审核的 hook 改成 Trusted,并收集 key 与 current_hash → 如果至少有一项,发送 TrustHooks 批量事件。

调用关系:handle_key_event 在事件总览页按 t 时调用它;上层收到 AppEvent 后负责真正记录这些信任。

调用图:调用 2 个内部函数(send, hook_needs_review);被 1 处调用(handle_key_event);外部调用 1 个(new)。

HooksBrowserView::close276–278 ↗
fn close(&mut self)

作用:标记这个浏览器视图已经完成,可以被底部面板关闭。

数据流:不需要输入 → 把 complete 设为 true → is_complete 之后会返回 true。

调用关系:handle_key_event 在总览页按取消键时调用它,on_ctrl_c 在 Ctrl+C 时也调用它。

调用图:被 2 处调用(handle_key_event, on_ctrl_c)。

HooksBrowserView::return_to_events280–293 ↗
fn return_to_events(&mut self)

作用:从某个事件的 hook 列表返回事件总览页,并尽量保持刚才那个事件仍被选中。

数据流:读取当前 page 里记录的事件名 → 切回 Events 页面,重置滚动状态 → 找回该事件在总览表里的位置并选中,找不到时选第一行。

调用关系:handle_key_event 在 Handlers 页按取消键时调用它;这样用户返回后不会迷失位置。

调用图:调用 1 个内部函数(new);被 1 处调用(handle_key_event)。

HooksBrowserView::event_header_lines295–302 ↗
fn event_header_lines() -> Vec<Line<'static>>

作用:生成事件总览页顶部的标题和说明文字。

数据流:不读取视图状态 → 创建“Hooks”和一行灰色说明 → 输出可渲染的文本行列表。

调用关系:event_page_lines 会把它放在总览页最上方。

调用图:外部调用 1 个(vec!)。

HooksBrowserView::review_needed_total_count304–310 ↗
fn review_needed_total_count(&self) -> usize

作用:统计当前所有 hook 中有多少个需要用户审核或重新信任。

数据流:读取 entry.hooks → 用 hook_needs_review 判断每个 hook → 输出待审核总数。

调用关系:event_page_lines 用它显示警告,render_footer 用它决定总览页底部是否提示“按 t 信任全部”。

调用图:被 2 处调用(event_page_lines, render_footer)。

HooksBrowserView::handler_header_lines313–327 ↗
fn handler_header_lines(
        event_name: HookEventName,
        review_needed_count: usize,
    ) -> Vec<Line<'static>>

作用:生成某个事件的 hook 列表页标题。它会根据是否有待审核 hook 显示普通说明或黄色警告。

数据流:输入事件名和待审核数量 → 先生成“某事件 hooks”的标题 → 调用 review_needed_message 得到警告文案,或显示启用/关闭会自动保存的说明 → 输出文本行。

调用关系:desired_height 和 render 都会用它;它和 review_needed_count 配合,让标题反映当前事件的安全状态。

调用图:调用 1 个内部函数(review_needed_message);外部调用 1 个(vec!)。

HooksBrowserView::review_needed_count329–333 ↗
fn review_needed_count(&self, event_name: HookEventName) -> usize

作用:统计某个事件下面有几个 hook 需要审核。

数据流:输入事件名 → 通过 handlers_for_event 取该事件的 hook → 用 hook_needs_review 过滤计数 → 输出数量。

调用关系:render 和 desired_height 用它生成 handler_header_lines,决定页面说明和高度。

调用图:调用 1 个内部函数(handlers_for_event);被 2 处调用(desired_height, render)。

HooksBrowserView::event_table_lines336–388 ↗
fn event_table_lines(&self) -> Vec<Line<'static>>

作用:把事件统计数据排成总览表。它负责列名、数字、描述、选中高亮和待审核颜色。

数据流:读取 event_rows 和当前选中行 → 判断是否需要显示 Review 列 → 为每个事件生成一行文本;选中行用强调色,普通数字和描述变暗,待审核数字变黄 → 输出可渲染的表格行。

调用关系:event_page_lines 会把这些表格行接到标题、警告和问题列表之后;它依赖 event_description、event_label 和 accent_style 做展示。

调用图:调用 3 个内部函数(event_rows, event_description, accent_style);被 1 处调用(event_page_lines);外部调用 5 个(from, from, new, format!, vec!)。

HooksBrowserView::event_issue_lines390–409 ↗
fn event_issue_lines(&self) -> Vec<Line<'static>>

作用:把加载 hook 时产生的警告和错误变成界面上的“Issues”区域。

数据流:读取 entry.warnings 和 entry.errors → 如果都为空就输出空列表;否则先放标题,再把警告加 ,把错误路径和消息标红 → 输出文本行。

调用关系:event_page_lines 会在总览页表格前插入这些问题,提醒用户某些 hook 配置没有正常加载。

调用图:被 1 处调用(event_page_lines);外部调用 1 个(new)。

HooksBrowserView::event_page_lines412–429 ↗
fn event_page_lines(&self) -> Vec<Line<'static>>

作用:组装完整的事件总览页内容。

数据流:先生成标题 → 如果有待审核 hook,就插入黄色警告 → 如果有配置问题,就插入 Issues 区域 → 最后加入事件表格 → 输出整页文本行。

调用关系:desired_height 用它算高度,render 用它画总览页;它把 event_header_lines、event_issue_lines、event_table_lines 串起来。

调用图:调用 4 个内部函数(event_issue_lines, event_table_lines, review_needed_total_count, review_needed_message);被 2 处调用(desired_height, render);外部调用 3 个(default, event_header_lines, format!)。

HooksBrowserView::handler_row_lines432–469 ↗
fn handler_row_lines(&self, event_name: HookEventName, width: usize) -> Vec<Line<'static>>

作用:生成某个事件下每条 hook 的列表行。它用 [x]、[!]、[ ] 这种标记告诉用户状态。

数据流:输入事件名和可用宽度 → 取出该事件的 hook → 对每条 hook 生成标题和状态后缀,比如 new、modified;超长内容会截断加省略号 → 根据选中、待审核、managed 状态加颜色或变暗 → 输出列表行。

调用关系:desired_height 用它算列表高度,render 用它画 hook 列表;它通过 handlers_for_event 只显示当前事件相关的 hook。

调用图:调用 1 个内部函数(handlers_for_event);被 2 处调用(desired_height, render)。

HooksBrowserView::detail_lines471–497 ↗
fn detail_lines(&self, event_name: HookEventName, width: usize) -> Vec<Line<'static>>

作用:生成当前选中 hook 的详情区。这里显示事件、匹配条件、来源、命令、超时和信任状态。

数据流:输入事件名和宽度 → 找当前选中的 hook;如果没有,就输出“没有安装 hook” → 否则把各字段格式化,长文本自动换行,命令最多显示三行 → 输出详情文本行。

调用关系:desired_height 和 render 都会用它;它依赖 detail_line、detail_wrapped_lines、detail_source_value 和 hook_trust_label 做可读展示。

调用图:调用 5 个内部函数(selected_hook, detail_line, detail_source_value, detail_wrapped_lines, hook_trust_label);被 2 处调用(desired_height, render);外部调用 2 个(format!, vec!)。

HooksBrowserView::handle_key_event563–604 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:处理用户在 Hooks 浏览器里按下的键。它是键盘操作的总调度处。

数据流:输入一个 KeyEvent → 按 keymap 或固定按键判断含义 → 调用移动、翻页、进入事件、切换 hook、信任、返回或关闭等函数 → 改变视图状态,并可能发送 AppEvent。

调用关系:底部面板的事件循环会调用它;它把具体工作分发给 move_up、open_selected_event、toggle_selected_hook、trust_all_hooks 等小函数。

调用图:调用 12 个内部函数(close, jump_bottom, jump_top, move_down, move_up, open_selected_event, page_down, page_up, return_to_events, toggle_selected_hook (+2 more))。

HooksBrowserView::is_complete606–608 ↗
fn is_complete(&self) -> bool

作用:告诉外层这个视图是否已经结束。外层可以据此关闭底部弹窗。

数据流:读取 complete 字段 → 原样返回 true 或 false → 不修改任何状态。

调用关系:BottomPaneView trait 的一部分;close 或 on_ctrl_c 会把 complete 设为 true。

HooksBrowserView::on_ctrl_c610–613 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理 Ctrl+C。这里选择关闭浏览器,并告诉外层这个 Ctrl+C 已经被处理了。

数据流:不需要额外输入 → 调用 close 把 complete 设为 true → 返回 CancellationEvent::Handled。

调用关系:外层收到 Ctrl+C 时会调用它;它复用 close,保证 Ctrl+C 和 Esc 关闭走相同状态。

调用图:调用 1 个内部函数(close)。

HooksBrowserView::prefer_esc_to_handle_key_event615–617 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

作用:声明 Esc 键应该优先交给这个视图处理。这样在详情页 Esc 可以先返回总览,而不是被外层直接关闭。

数据流:不读取状态 → 固定返回 true → 不产生副作用。

调用关系:底部面板按键分发时会看这个结果;测试 esc_routes_through_the_view 会确认这个约定。

HooksBrowserView::desired_height621–643 ↗
fn desired_height(&self, width: u16) -> u16

作用:计算这个弹窗理想情况下需要多高。这样外层布局能给它合适的空间。

数据流:输入可用宽度 → 根据当前页面计算标题、列表、详情和 footer 需要的行数 → 输出 u16 高度,过大时饱和到最大值。

调用关系:测试辅助 render_lines 和 render_buffer 会先调用它;真实渲染系统也可用它安排底部面板尺寸。

调用图:调用 4 个内部函数(detail_lines, event_page_lines, handler_row_lines, review_needed_count);被 2 处调用(render_buffer, render_lines);外部调用 1 个(handler_header_lines)。

HooksBrowserView::render645–695 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:真正把 Hooks 浏览器画到终端缓冲区。它把内容区、列表区、详情区和底部提示组合成完整界面。

数据流:输入绘制区域和 Buffer → 如果区域为空就退出;否则画弹窗表面,按当前页面生成文本;Handlers 页还会把头部、可滚动列表、详情区分开画 → 最后画 footer,修改 Buffer。

调用关系:Renderable trait 的核心方法;外层每次刷新终端时调用它,它再调用 event_page_lines、handler_row_lines、detail_lines 和 render_footer。

调用图:调用 6 个内部函数(detail_lines, event_page_lines, handler_row_lines, render_footer, review_needed_count, render_menu_surface);被 2 处调用(render_buffer, render_lines);外部调用 9 个(Fill, Length, vertical, default, from, new, is_empty, handler_header_lines, vec!)。

hook_is_active698–704 ↗
fn hook_is_active(hook: &HookMetadata) -> bool

作用:判断一个 hook 现在是否真的算“可运行”。不是只看 enabled,还要看它是否已经可信或由管理员管理。

数据流:输入 HookMetadata → 检查 enabled 是否为 true,且 trust_status 是 Managed 或 Trusted → 输出布尔值。

调用关系:event_rows 用它统计 Active 列;这保证待审核的 hook 即使 enabled 也不会被误算为 active。

调用图:外部调用 1 个(matches!)。

review_needed_message706–712 ↗
fn review_needed_message(count: usize) -> Option<String>

作用:把待审核数量变成给用户看的英文提示。数量为 0 时不显示。

数据流:输入 count → 0 返回 None,1 返回单数句子,大于 1 返回复数句子 → 输出可选字符串。

调用关系:event_page_lines 和 handler_header_lines 用它生成黄色警告文案。

调用图:被 2 处调用(event_page_lines, handler_header_lines);外部调用 1 个(format!)。

hook_trust_label721–728 ↗
fn hook_trust_label(status: HookTrustStatus) -> &'static str

作用:把 hook 的信任状态翻译成详情区里容易读懂的标签。

数据流:输入 HookTrustStatus → 匹配 Managed、Trusted、Untrusted、Modified → 输出对应静态文字。

调用关系:detail_lines 用它显示 Trust 字段,让用户知道为什么某个 hook 需要审核。

调用图:被 1 处调用(detail_lines)。

event_label730–743 ↗
fn event_label(event_name: HookEventName) -> &'static str

作用:把事件枚举值变成界面上显示的事件名称。

数据流:输入 HookEventName → 按具体事件返回固定英文名,比如 PreToolUse、SessionStart → 输出静态字符串。

调用关系:事件总览、handler 标题和详情行都会间接使用它来保持名称一致。

event_description745–758 ↗
fn event_description(event_name: HookEventName) -> &'static str

作用:给每种 hook 事件配一句简短说明,告诉外行它在什么时候触发。

数据流:输入 HookEventName → 返回对应描述,比如“工具执行前”“权限申请时” → 输出静态字符串。

调用关系:event_table_lines 用它填充总览表的 Description 列。

调用图:被 1 处调用(event_table_lines)。

hook_title760–762 ↗
fn hook_title(idx: usize) -> String

作用:给某个事件下的第 N 个 hook 生成简单标题,比如 Hook 1、Hook 2。

数据流:输入从 0 开始的下标 → 加 1 后格式化 → 输出标题字符串。

调用关系:handler_row_lines 用它显示 hook 列表,避免直接暴露复杂 key。

调用图:外部调用 1 个(format!)。

hook_source_summary764–773 ↗
fn hook_source_summary(hook: &HookMetadata) -> String

作用:生成 hook 来源的简短版本,特别处理插件来源。它让列表或详情里能看出 hook 是从哪里来的。

数据流:输入 HookMetadata → 如果来源是 Plugin,就优先显示插件 id;否则调用 config_source_label → 输出来源摘要字符串。

调用关系:detail_source_value 在插件 hook 情况下会调用它;普通配置来源则走 config_source_label。

调用图:调用 1 个内部函数(config_source_label);被 1 处调用(detail_source_value)。

detail_source_value775–790 ↗
fn detail_source_value(hook: &HookMetadata) -> String

作用:生成详情区 Source 字段的完整显示文本。它会说明来源类型,必要时还显示配置文件路径。

数据流:输入 HookMetadata → 插件来源显示插件摘要;管理员/云管理等固定来源只显示标签;用户/项目等文件来源显示“来源标签 - 路径” → 输出字符串。

调用关系:detail_lines 用它填 Source;它调用 hook_source_summary、config_source_label 和 format_directory_display。

调用图:调用 2 个内部函数(config_source_label, hook_source_summary);被 1 处调用(detail_lines);外部调用 1 个(format!)。

config_source_label792–806 ↗
fn config_source_label(source: HookSource) -> &'static str

作用:把 hook 配置来源枚举翻译成人能看懂的来源名称,比如用户配置、项目配置、管理员配置。

数据流:输入 HookSource → 按来源返回固定标签;Plugin 分支理论上不该到这里,到了就触发 unreachable → 输出静态字符串。

调用关系:hook_source_summary 和 detail_source_value 都用它统一来源文案。

调用图:被 2 处调用(detail_source_value, hook_source_summary);外部调用 1 个(unreachable!)。

detail_line808–810 ↗
fn detail_line(label: &str, value: &str) -> Line<'static>

作用:生成详情区里一行简单的“标签 + 值”。

数据流:输入 label 和 value → label 固定宽度左对齐,value 变暗显示 → 输出一行 Line。

调用关系:detail_lines 用它显示 Event、Timeout、Trust 这类短字段。

调用图:被 1 处调用(detail_lines);外部调用 2 个(from, vec!)。

detail_wrapped_lines812–854 ↗
fn detail_wrapped_lines(
    label: &str,
    value: &str,
    width: usize,
    max_lines: Option<usize>,
) -> Vec<Line<'static>>

作用:生成详情区里可能很长的字段,并按宽度自动换行。必要时会限制最大行数并加省略号。

数据流:输入标签、文本、可用宽度和可选最大行数 → 算出值部分宽度,用 textwrap 换行;超过最大行数就截断最后一行并补省略号 → 输出多行 Line。

调用关系:detail_lines 用它显示 Matcher、Source、Command;它还调用 truncate_line_with_ellipsis_if_overflow 处理最后一行的视觉截断。

调用图:调用 1 个内部函数(truncate_line_with_ellipsis_if_overflow);被 1 处调用(detail_lines);外部调用 4 个(from, format!, wrap, vec!)。

tests::render_lines881–906 ↗
fn render_lines(view: &HooksBrowserView, width: u16) -> String

作用:测试辅助函数:把视图渲染成普通字符串,方便做快照对比。

数据流:输入一个视图和宽度 → 用 desired_height 算高度,创建空 Buffer,调用 render → 逐格读取字符并标准化测试路径显示 → 输出多行字符串。

调用关系:大量 snapshot 测试调用它,确认终端界面长什么样。

调用图:调用 2 个内部函数(desired_height, render);外部调用 2 个(empty, new)。

tests::render_buffer908–914 ↗
fn render_buffer(view: &HooksBrowserView, width: u16) -> Buffer

作用:测试辅助函数:把视图渲染成 Buffer,方便检查颜色和样式。

数据流:输入视图和宽度 → 计算高度,创建空 Buffer,调用 render → 输出完整 Buffer。

调用关系:selected_event_rows_use_the_shared_accent_style 用它检查选中行是否用了统一强调样式。

调用图:调用 2 个内部函数(desired_height, render);外部调用 2 个(empty, new)。

tests::hook917–949 ↗
fn hook(
        key: &str,
        event_name: HookEventName,
        source: HookSource,
        plugin_id: Option<&str>,
        command: &str,
        enabled: bool,
        is_managed: bool,

作用:测试辅助函数:快速造出一条 HookMetadata,避免每个测试重复写很多字段。

数据流:输入 key、事件、来源、插件 id、命令、启用状态、是否 managed、显示顺序 → 填入默认 matcher、timeout、hash、路径和信任状态 → 输出 HookMetadata。

调用关系:几乎所有 hook 行为测试都会用它准备样例数据。

调用图:外部调用 1 个(test_path_buf)。

tests::view951–990 ↗
fn view() -> HooksBrowserView

作用:测试辅助函数:造一个带三条典型 hook 的浏览器视图。里面包含插件 hook、用户配置 hook 和管理员 hook。

数据流:创建测试用事件通道 → 用 tests::hook 造多条 hook → 调用 HooksBrowserView::new → 输出可直接渲染和操作的视图。

调用关系:基础渲染、managed hook、返回选择位置等测试都会用它作为默认场景。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(new, vec!)。

tests::renders_event_browser993–996 ↗
fn renders_event_browser()

作用:确认事件总览页能按预期渲染。

数据流:创建默认测试视图 → 渲染成字符串 → 和保存的快照比较。

调用关系:它保护 event_page_lines、event_table_lines 和 render 的整体输出不被无意改坏。

调用图:外部调用 2 个(assert_snapshot!, view)。

tests::selected_event_rows_use_the_shared_accent_style999–1016 ↗
fn selected_event_rows_use_the_shared_accent_style()

作用:确认选中的事件行使用项目统一的强调样式。

数据流:渲染默认视图到 Buffer → 找到选中行里的特定字符单元格 → 比较它的前景色和粗体修饰 → 测试通过或失败。

调用关系:它直接验证 event_table_lines 和 render 输出的样式,防止选中高亮和其他界面风格不一致。

调用图:调用 1 个内部函数(accent_style);外部调用 3 个(assert_eq!, render_buffer, view)。

tests::renders_event_browser_with_review_column_when_needed1019–1053 ↗
fn renders_event_browser_with_review_column_when_needed()

作用:确认有待审核 hook 时,总览页会出现 Review 列并用醒目样式显示。

数据流:造一条 Untrusted hook → 创建视图 → 渲染快照,并检查 Review 单元格颜色和粗体 → 输出测试断言结果。

调用关系:它覆盖 review_needed_message、event_rows、event_table_lines 的待审核路径。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(new, assert!, assert_eq!, assert_snapshot!, hook, vec!)。

tests::renders_event_browser_with_issues1056–1072 ↗
fn renders_event_browser_with_issues()

作用:确认配置警告和错误会显示在总览页的 Issues 区域。

数据流:创建带 warning 和 error 的视图 → 渲染字符串 → 与快照比较。

调用关系:它主要验证 event_issue_lines 被 event_page_lines 正确插入。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_snapshot!, vec!)。

tests::renders_handler_browser_with_details1075–1079 ↗
fn renders_handler_browser_with_details()

作用:确认从总览页进入 hook 列表后,会显示 hook 列表和详情区。

数据流:创建默认视图 → 模拟按 Enter 进入选中事件 → 渲染字符串 → 做快照比较。

调用关系:它覆盖 handle_key_event、open_selected_event、handler_row_lines、detail_lines 和 render 的配合。

调用图:外部调用 3 个(from, assert_snapshot!, view)。

tests::renders_untrusted_enabled_handler_as_inactive1082–1107 ↗
fn renders_untrusted_enabled_handler_as_inactive()

作用:确认一个虽然 enabled 但未信任的 hook 不会被显示成真正启用状态。

数据流:造一条 enabled=true 且 Untrusted 的 hook → 进入 handler 页 → 渲染快照 → 验证显示符合“待审核、不可运行”的预期。

调用关系:它保护 hook_is_active 和 handler_row_lines 对信任状态的解释。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(from, new, assert_snapshot!, hook, vec!)。

tests::review_needed_handler_rows_use_warning_color1110–1149 ↗
fn review_needed_handler_rows_use_warning_color()

作用:确认需要审核的 hook 行会用黄色警告色。

数据流:创建一条 trusted hook 和一条 untrusted hook → 进入 handler 页 → 读取第二行样式 → 断言前景色是黄色。

调用关系:它直接验证 handler_row_lines 对 hook_needs_review 的样式处理。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(from, new, assert_eq!, hook, vec!)。

tests::review_needed_handler_header_uses_warning_color1152–1163 ↗
fn review_needed_handler_header_uses_warning_color()

作用:确认 handler 页标题下的待审核提示也是黄色。

数据流:直接调用 handler_header_lines,传入待审核数量 1 → 读取第二行样式 → 断言颜色为黄色。

调用关系:它覆盖 handler_header_lines 和 review_needed_message 的警告分支。

调用图:外部调用 1 个(assert_eq!)。

tests::renders_managed_handler_without_toggle_hint1166–1174 ↗
fn renders_managed_handler_without_toggle_hint()

作用:确认管理员管理的 hook 不会显示“可切换开关”的提示。

数据流:创建默认视图 → 移到 PermissionRequest 事件并进入 → 渲染快照 → 检查底部提示符合 managed hook 的规则。

调用关系:它验证 render_footer 会根据 selected_hook.is_managed 改变提示。

调用图:外部调用 3 个(from, assert_snapshot!, view)。

tests::renders_selected_managed_handler1177–1212 ↗
fn renders_selected_managed_handler()

作用:确认多个 managed hook 中选中某一条时,界面显示仍正确。

数据流:创建两条管理员 hook → 进入列表并向下移动选中第二条 → 渲染快照。

调用关系:它覆盖 handler_row_lines 的 managed 变暗和选中样式组合。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(from, new, assert_snapshot!, vec!)。

tests::renders_scrolled_handler_window1215–1241 ↗
fn renders_scrolled_handler_window()

作用:确认 hook 列表超过最大可见行数时可以滚动显示。

数据流:创建比 MAX_POPUP_ROWS 多一条的 hook 列表 → 进入 handler 页并多次向下移动 → 渲染快照。

调用关系:它验证 move_down、ScrollState 和 render 中 skip/take 可见行的配合。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(from, new, assert_snapshot!)。

tests::renders_command_details_with_three_line_cap1244–1268 ↗
fn renders_command_details_with_three_line_cap()

作用:确认详情区里的长命令最多显示三行,并用省略号收尾。

数据流:创建一条命令很长的 hook,并把宽度设得较窄 → 进入详情页 → 渲染快照。

调用关系:它主要保护 detail_wrapped_lines 在 max_lines=3 时的截断逻辑。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(from, new, assert_snapshot!, test_path_buf, hook, vec!)。

tests::renders_empty_handler_browser_message1271–1285 ↗
fn renders_empty_handler_browser_message()

作用:确认某个事件没有安装 hook 时,会显示友好的空状态提示。

数据流:创建空 hook 视图 → 移动到某事件并进入 → 渲染快照。

调用关系:它覆盖 render 在 rows.is_empty() 分支下的显示。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(from, new, assert_snapshot!)。

tests::managed_hooks_count_as_active1288–1314 ↗
fn managed_hooks_count_as_active()

作用:确认管理员管理且启用的 hook 会被统计为 active。

数据流:创建一条 managed 且 enabled 的 hook → 调用 event_rows → 找到对应事件行 → 断言 installed=1、active=1。

调用关系:它验证 hook_is_active 对 HookTrustStatus::Managed 的处理。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(new, assert_eq!, vec!)。

tests::review_needed_hooks_are_not_active1317–1346 ↗
fn review_needed_hooks_are_not_active()

作用:确认需要审核的 hook 不会被统计为 active,即使 enabled=true。

数据流:创建一条 Untrusted 且 enabled 的 hook → 调用 event_rows → 找到对应事件 → 断言 installed=1、active=0、needs_review=1。

调用关系:它保护 event_rows、hook_is_active 和 hook_needs_review 的组合语义。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_eq!, hook, vec!)。

tests::review_needed_event_is_selected_by_default1349–1373 ↗
fn review_needed_event_is_selected_by_default()

作用:确认打开浏览器时,如果有事件包含待审核 hook,会默认选中那个事件。

数据流:创建一条 PermissionRequest 的 Untrusted hook → 创建视图 → 调用 selected_event → 断言选中的是 PermissionRequest。

调用关系:它验证 HooksBrowserView::from_entry 的默认选中策略。

调用图:调用 2 个内部函数(new, new);外部调用 4 个(new, assert_eq!, hook, vec!)。

tests::renders_review_needed_handler1376–1401 ↗
fn renders_review_needed_handler()

作用:确认待审核 hook 的详情页显示正确,包括 new/modified 状态和信任提示。

数据流:创建一条 Untrusted hook → 进入 handler 页 → 渲染快照。

调用关系:它覆盖 handler_header_lines、handler_row_lines、detail_lines 和 render_footer 的待审核分支。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(from, new, assert_snapshot!, hook, vec!)。

tests::assert_unmanaged_toggle_key1403–1430 ↗
fn assert_unmanaged_toggle_key(key_code: KeyCode)

作用:测试辅助断言:确认某个按键能切换普通非 managed hook。

数据流:输入要测试的 KeyCode → 创建一条可切换插件 hook 和事件接收端 → 进入 handler 页后按该键 → 从通道读取 AppEvent,断言发送的是 SetHookEnabled 且 enabled 变为 false。

调用关系:toggle_keys_toggle_unmanaged_handler 用它分别测试空格和回车;它覆盖 handle_key_event 到 toggle_selected_hook 再到 AppEvent 的链路。

调用图:调用 2 个内部函数(new, new);外部调用 6 个(from, new, assert!, assert_eq!, panic!, vec!)。

tests::toggle_keys_toggle_unmanaged_handler1433–1437 ↗
fn toggle_keys_toggle_unmanaged_handler()

作用:确认空格和回车都能切换普通 hook。

数据流:遍历空格键和 Enter 键 → 对每个键调用 assert_unmanaged_toggle_key → 如果任一键没有发送正确事件,测试失败。

调用关系:它复用 assert_unmanaged_toggle_key,验证 handle_key_event 对两种切换键的支持。

调用图:外部调用 2 个(Char, assert_unmanaged_toggle_key)。

tests::space_does_not_toggle_managed_handler1440–1461 ↗
fn space_does_not_toggle_managed_handler()

作用:确认空格不能关闭管理员管理的 hook。

数据流:创建一条 managed hook → 进入 handler 页并按空格 → 尝试从事件通道收消息 → 断言没有任何 SetHookEnabled 事件。

调用关系:它保护 toggle_selected_hook 里 is_managed 的提前返回规则。

调用图:调用 2 个内部函数(new, new);外部调用 5 个(Char, from, new, assert!, vec!)。

tests::trust_key_trusts_review_needed_handler_without_changing_enablement1464–1497 ↗
fn trust_key_trusts_review_needed_handler_without_changing_enablement()

作用:确认在 handler 页按 t 会信任当前待审核 hook,但不会顺便改变它的启用状态。

数据流:创建一条 disabled 且 Untrusted 的 hook → 进入 handler 页按 t → 从事件通道读取 TrustHook,检查 key 和 hash 正确。

调用关系:它验证 handle_key_event 的 t 键会走 trust_selected_hook,而不是 toggle_selected_hook。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(Char, from, new, assert_eq!, panic!, hook, vec!)。

tests::trust_key_preserves_disabled_modified_handler1500–1536 ↗
fn trust_key_preserves_disabled_modified_handler()

作用:确认被修改过且处于关闭状态的 hook 被信任后,仍然保持关闭。

数据流:创建一条 disabled 且 Modified 的 hook → 进入 handler 页按 t → 检查本地 hook 仍 disabled、trust_status 变 Trusted,并收到 TrustHook 事件。

调用关系:它保护 trust_selected_hook 只改信任状态,不改 enabled。

调用图:调用 2 个内部函数(new, new);外部调用 8 个(Char, from, new, assert!, assert_eq!, panic!, hook, vec!)。

tests::trust_key_on_event_page_trusts_all_review_needed_hooks1539–1614 ↗
fn trust_key_on_event_page_trusts_all_review_needed_hooks()

作用:确认在事件总览页按 t 会批量信任所有需要审核的 hook。

数据流:创建 Untrusted、Modified 和 Trusted 三条 hook → 在总览页按 t → 检查所有本地状态都变 Trusted,并且事件通道只收到包含前两条的 TrustHooks 批量事件。

调用关系:它验证 handle_key_event 在 Events 页的 t 键会调用 trust_all_hooks。

调用图:调用 2 个内部函数(new, new);外部调用 8 个(Char, from, new, assert!, assert_eq!, panic!, hook, vec!)。

tests::escape_returns_to_the_selected_event1617–1628 ↗
fn escape_returns_to_the_selected_event()

作用:确认从 handler 页按 Esc 返回总览时,仍选中刚才进入的事件。

数据流:创建默认视图 → 先向下选中 PermissionRequest,再进入,再按 Esc → 检查 page 回到 Events,selected_event 仍是 PermissionRequest。

调用关系:它保护 return_to_events 的“记住原事件”行为。

调用图:外部调用 3 个(from, assert_eq!, view)。

tests::esc_routes_through_the_view1631–1633 ↗
fn esc_routes_through_the_view()

作用:确认这个视图声明自己优先处理 Esc。

数据流:创建默认视图 → 调用 prefer_esc_to_handle_key_event → 断言返回 true。

调用关系:它保护 BottomPaneView 的按键分发约定,确保 Esc 能在本视图里用于返回上一层。

调用图:外部调用 1 个(assert!)。

模态输入和反馈视图

此组包含较小的底部窗格文本输入和反馈类模态视图,用于聚焦的用户提示和笔记记录。

tui/src/bottom_pane/custom_prompt_view.rs源码 ↗
domain_logic当底部弹窗式自定义输入框打开后,在界面刷新和键盘输入处理中活跃

这个文件像一个小弹窗表单,专门收集用户临时输入的一段说明,比如评审要求、重命名内容、目标修改等。没有它,很多需要用户补充文字的功能就只能硬编码,或者没法在终端里友好输入。它里面的 CustomPromptView 保存标题、占位提示、可选的上下文说明、输入框内容和提交回调。运行时,它一边接收键盘事件,一边决定是把字符放进文本框、插入换行、提交内容,还是取消弹窗。它还特别照顾“粘贴大段文字”的情况:PasteBurst 会判断用户是不是正在快速粘贴,避免把粘贴里的回车误当成提交。渲染时,它画标题、上下文、输入区域、灰色占位文字和底部快捷键提示,并告诉外层光标应该显示在哪里。

函数细节12
CustomPromptView::new46–69 ↗
fn new(
        title: String,
        placeholder: String,
        initial_text: String,
        context_label: Option<String>,
        on_submit: PromptSubmitted,
    ) -> Self

作用:创建一个新的自定义提示词输入框。调用方给它标题、占位文字、初始内容和提交后要做的事,它就组装出一个可以显示、可以输入、可以提交的视图。

数据流:进去的是标题、提示文字、已有文本、可选的上下文标签,以及提交回调函数 → 它创建文本框,如果有初始内容就填进去并把光标放到末尾,同时准备输入状态和粘贴检测器 → 出来的是一个完整的 CustomPromptView,等待被显示和使用。

调用关系:它是这个小弹窗的入口工厂。custom_prompt_view、show_goal_edit_prompt、show_rename_prompt、open_marketplace_add_prompt、show_review_custom_prompt 这些地方需要向用户要一段文字时,会先调用它来造出界面。

调用图:调用 1 个内部函数(new);被 5 处调用(custom_prompt_view, show_goal_edit_prompt, show_rename_prompt, open_marketplace_add_prompt, show_review_custom_prompt);外部调用 3 个(new, default, default)。

CustomPromptView::handle_key_event_at71–125 ↗
fn handle_key_event_at(&mut self, key_event: KeyEvent, now: Instant)

作用:处理一次键盘按键,并根据按键决定输入、提交、换行或取消。这里是这个输入框最核心的交互判断。

数据流:进去的是一个按键事件和当前时间 → 它检查是不是 Esc、回车、普通字符、Tab 或其他键;必要时参考 PasteBurst 判断是不是粘贴中的连续输入 → 出来可能是文本框内容变化、窗口被标记为已提交或已取消,也可能只是普通移动光标或编辑文本。

调用关系:handle_key_event 会把真实时间加上后交给它。它会把普通编辑动作交给 TextArea,把粘贴节奏判断交给 PasteBurst;遇到 Esc 时会调用 on_ctrl_c;遇到普通回车且内容不空时会调用外部传进来的提交回调。

调用图:调用 10 个内部函数(on_ctrl_c, clear_after_explicit_paste, direct_insert_newline_should_insert, extend_window, on_plain_char_no_hold, allows_paste_burst, input, insert_str, text, has_ctrl_or_alt);被 1 处调用(handle_key_event)。

CustomPromptView::handle_key_event129–131 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:接收外层界面传来的键盘事件,并用当前时间去处理它。它是对外暴露的按键处理入口。

数据流:进去的是用户刚按下的键 → 它读取当前时间 Instant::now → 把按键和时间一起交给 handle_key_event_at,实际结果由后者改变输入框状态。

调用关系:它实现 BottomPaneView 这个底部面板接口,所以外层主界面不需要知道细节,只要把按键交给它。它自己不做复杂判断,只负责把活儿转给 handle_key_event_at。

调用图:调用 1 个内部函数(handle_key_event_at);外部调用 1 个(now)。

CustomPromptView::on_ctrl_c133–136 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:把这个输入框标记为“用户取消了”。这里虽然名字叫 Ctrl-C,但 Esc 也会走这条取消路径。

数据流:进去不需要额外数据 → 它把 completion 设置成 Cancelled → 出来返回 CancellationEvent::Handled,告诉外层这个取消动作已经由本视图处理了。

调用关系:handle_key_event_at 在用户按 Esc 时会调用它。外层也可以通过 BottomPaneView 的取消流程调用它,用来统一关闭这个底部输入弹窗。

调用图:被 1 处调用(handle_key_event_at)。

CustomPromptView::is_complete138–140 ↗
fn is_complete(&self) -> bool

作用:告诉外层:这个输入框是不是已经结束了。结束可能是用户提交了,也可能是用户取消了。

数据流:进去不需要参数 → 它查看 completion 有没有值 → 出来是 true 或 false,true 表示外层可以收起这个视图。

调用关系:外层底部面板循环会用它判断当前视图是否还要继续显示。它只读状态,不触发提交或取消。

CustomPromptView::completion142–144 ↗
fn completion(&self) -> Option<ViewCompletion>

作用:告诉外层这个输入框是怎么结束的:接受还是取消。它比 is_complete 多给出结束原因。

数据流:进去不需要参数 → 它读取 completion 字段 → 出来是 Some(Accepted)、Some(Cancelled) 或 None。

调用关系:外层在发现 is_complete 为 true 后,可以调用它决定下一步怎么收尾,比如继续执行提交后的动作,或者只是关闭弹窗。

CustomPromptView::handle_paste146–153 ↗
fn handle_paste(&mut self, pasted: String) -> bool

作用:处理用户一次性粘贴进来的文字。它直接把整段文字放进输入框,而不是一个键一个键地猜。

数据流:进去的是粘贴得到的字符串 → 如果是空字符串就什么也不做并返回 false;否则把文字插入 TextArea,并清掉 PasteBurst 的显式粘贴状态 → 出来返回 true,表示这次粘贴已经处理。

调用关系:它是 BottomPaneView 提供给外层粘贴通道的接口。显式粘贴走这里后,会清理 PasteBurst,避免后续按键被误判成同一次快速粘贴。

调用图:调用 2 个内部函数(clear_after_explicit_paste, insert_str)。

CustomPromptView::desired_height161–164 ↗
fn desired_height(&self, width: u16) -> u16

作用:计算这个输入弹窗希望占多少行高度。外层排版时用它来给底部面板留空间。

数据流:进去的是可用宽度 → 它根据有没有上下文标签、输入框需要的高度、标题和提示行来相加 → 出来是一个行数。

调用关系:外层渲染布局会询问它需要多高。它把输入区高度的细节交给 input_height,然后在外面加上标题、上下文和底部提示所需的行。

调用图:调用 1 个内部函数(input_height)。

CustomPromptView::render166–266 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把这个输入框真正画到终端屏幕缓冲区里。用户看到的标题、输入区、占位提示和快捷键提示都由它画出来。

数据流:进去的是一块屏幕区域和可写的 Buffer(终端画布)→ 它先检查区域是否有效,再计算输入区高度,依次画标题、可选上下文、左侧彩色竖线、文本框、空白行和底部提示 → 出来是 Buffer 被改写,下一次刷新时用户能看到完整弹窗。

调用关系:它实现 Renderable,所以外层界面刷新时会调用它。它会使用 input_height 决定输入框大小,用 TextArea 的渲染能力画正文,并调用 standard_popup_hint_line 生成底部快捷键说明。

调用图:调用 3 个内部函数(input_height, standard_popup_hint_line, text);外部调用 4 个(from, new, render_ref, vec!)。

CustomPromptView::cursor_pos268–286 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:告诉外层光标应该显示在屏幕的哪个坐标。没有它,用户输入时可能看不到光标跟着文字走。

数据流:进去的是整个弹窗所在的屏幕区域 → 它先排除太小的区域,再算出真正的文本框区域和高度,读取 TextAreaState → 出来是一个可选坐标;如果空间不够就返回 None。

调用关系:外层终端渲染完成后会用它放置光标。它把具体“文本里的光标映射到屏幕坐标”这件事交给 TextArea::cursor_pos_with_state。

调用图:调用 2 个内部函数(input_height, cursor_pos_with_state)。

CustomPromptView::input_height290–294 ↗
fn input_height(&self, width: u16) -> u16

作用:计算输入区域本身要占多少行。它保证输入框不会太矮,也不会无限长到把弹窗撑爆。

数据流:进去的是总宽度 → 它扣掉左侧装饰栏占用的宽度,再询问 TextArea 在这个宽度下希望多高,把结果限制在 1 到 8 行正文之间,最后再加一行顶部空白 → 出来是最多 9 行的输入区高度。

调用关系:desired_height、render 和 cursor_pos 都会调用它,因为这三个动作都必须对输入框高度有同一个判断,避免画出来的位置和光标位置对不上。

调用图:调用 1 个内部函数(desired_height);被 3 处调用(cursor_pos, desired_height, render)。

gutter297–299 ↗
fn gutter() -> Span<'static>

作用:生成输入框左侧那段青色的装饰符号。它让弹窗看起来有统一的边栏样式。

数据流:进去不需要参数 → 它把字符串“▌ ”染成青色 → 出来是一个 Span,也就是一小段带样式的可绘制文字。

调用关系:render 在画标题、上下文和输入区左侧边栏时会用到它。它只是一个小工具,避免同样的装饰写得到处都是。

tui/src/bottom_pane/feedback_view.rs源码 ↗
domain_logic用户打开反馈菜单、确认上传日志、填写备注、提交反馈和显示反馈结果时活跃

用户在终端里觉得结果不好、有 bug、被安全检查误拦,或者想夸一下时,需要一个简单、不打断主流程的反馈入口。这个文件就是这套入口的界面零件。它先提供分类选择菜单,再提供“是否上传日志”的确认菜单,最后弹出一个小输入框让用户写备注。按 Enter 会提交,按 Esc 或 Ctrl-C 会关掉。它不会自己上传文件,而是发出 AppEvent(一种应用内部消息,像把工单递给前台)让别的部分处理。它还会根据用户身份生成不同的后续链接:外部用户去 GitHub issue,OpenAI 员工走内部反馈链接。一个重要细节是:好评不会显示排查网络的诊断细节,也不会要求开 issue;而问题类反馈会尽量带上线程 ID,方便团队找到对应会话。

函数细节43
FeedbackNoteView::new61–76 ↗
fn new(
        category: FeedbackCategory,
        turn_id: Option<String>,
        app_event_tx: AppEventSender,
        include_logs: bool,
    ) -> Self

作用:创建一个新的反馈备注输入框。调用者会告诉它反馈属于哪一类、对应哪轮对话、是否要带日志,以及之后该把提交消息发给谁。

数据流:进去的是反馈分类、可选的 turn_id、应用消息发送器和是否带日志的开关 → 它把这些保存起来,并新建一个空文本框和默认光标状态 → 出来的是一个还没完成、等待用户输入的 FeedbackNoteView。

调用关系:当应用要显示“再说两句”的反馈输入框时会调用它;测试里的 make_view 和几个提交测试也用它造出界面对象。它内部借用 TextArea::new 和默认状态来准备输入区域。

调用图:调用 1 个内部函数(new);被 5 处调用(feedback_view_with_connectivity_diagnostics, make_view, submit_feedback_emits_submit_event_with_trimmed_note, submit_feedback_omits_empty_note, show_feedback_note);外部调用 2 个(new, default)。

FeedbackNoteView::submit78–88 ↗
fn submit(&mut self)

作用:把用户写的反馈备注提交出去。它会自动去掉前后空格,空备注就当作没有备注。

数据流:进去的是当前界面里文本框保存的文字,以及构造时保存的分类、turn_id、带日志开关 → 它读取文本、修剪空格、包装成 SubmitFeedback 应用事件并发送 → 结果是后台收到提交请求,同时这个小窗口被标记为完成。

调用关系:用户在反馈输入框里按普通 Enter 时,handle_key_event 会把流程交给它。它不直接上传,只通过 AppEventSender 把活交给应用的反馈上传流程。

调用图:调用 2 个内部函数(send, text);被 1 处调用(handle_key_event)。

FeedbackNoteView::handle_key_event92–116 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:处理用户在反馈备注框里按下的键。它决定这个键是提交、取消,还是继续输入文字。

数据流:进去的是一个键盘事件 → 如果是 Esc,就走取消;如果是不带修饰键的 Enter,就提交;如果是带修饰键的 Enter 或其他键,就交给文本框处理 → 出来可能是窗口完成,也可能只是文本内容变化。

调用关系:这是底部面板接收键盘操作时会调用的入口。它会在需要时调用 on_ctrl_c、submit,或者把输入交给 TextArea 的 input。

调用图:调用 3 个内部函数(on_ctrl_c, submit, input)。

FeedbackNoteView::on_ctrl_c118–121 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理取消操作。用户按 Esc 或类似取消键时,它让反馈窗口关闭,而不是把取消继续传给外层。

数据流:进去的是当前反馈窗口状态 → 它把 complete 设为 true → 出来是 CancellationEvent::Handled,表示这次取消已经被这个窗口处理掉了。

调用关系:handle_key_event 遇到 Esc 时会调用它;外层底部面板也可以把 Ctrl-C 类取消动作交给它。

调用图:被 1 处调用(handle_key_event)。

FeedbackNoteView::is_complete123–125 ↗
fn is_complete(&self) -> bool

作用:告诉外层这个反馈小窗口是不是已经结束了。结束可能是提交了,也可能是用户取消了。

数据流:进去的是当前窗口对象 → 它读取 complete 标记 → 出来是 true 或 false,表示外层是否可以把这个窗口收起来。

调用关系:底部面板框架会用它判断生命周期。submit 和 on_ctrl_c 会改变它读取的那个 complete 标记。

FeedbackNoteView::handle_paste127–133 ↗
fn handle_paste(&mut self, pasted: String) -> bool

作用:处理用户粘贴进来的文字。非空内容会直接插入到备注输入框里。

数据流:进去的是一段粘贴文本 → 如果为空,什么都不做并返回 false;如果有内容,就插入文本框 → 出来是 true,表示粘贴已被这个窗口接收。

调用关系:当底部面板收到粘贴事件时会调用它。它把具体插入动作交给 TextArea 的 insert_str。

调用图:调用 1 个内部函数(insert_str)。

FeedbackNoteView::desired_height137–139 ↗
fn desired_height(&self, width: u16) -> u16

作用:告诉界面布局系统:这个反馈输入框想占多高。这样外层才能给它留出合适空间。

数据流:进去的是可用宽度 → 它计算标题行数量、输入框高度和提示行空间 → 出来是建议高度。

调用关系:渲染测试和布局流程会调用它。它依赖 intro_lines 算标题高度,依赖 input_height 算输入区域高度。

调用图:调用 2 个内部函数(input_height, intro_lines);被 1 处调用(render)。

FeedbackNoteView::cursor_pos141–158 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:算出光标应该显示在屏幕的哪个位置。没有这个,用户输入时可能看不到光标在哪。

数据流:进去的是这个窗口实际占用的屏幕矩形 → 它扣掉标题和边距,得到文本框所在区域,再结合文本框状态计算光标坐标 → 出来是可选坐标;空间太小时返回 None。

调用关系:终端渲染框架需要显示光标时会调用它。它和 render 使用同一套 intro_lines、input_height 规则,保证光标位置和画出来的文本框一致。

调用图:调用 3 个内部函数(input_height, intro_lines, cursor_pos_with_state)。

FeedbackNoteView::render160–249 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把反馈备注窗口真正画到终端屏幕上。包括标题、左侧彩色竖线、输入框、占位提示和底部快捷键提示。

数据流:进去的是屏幕区域和绘图缓冲区 Buffer(可以理解成一张待写的字符画纸)→ 它先画标题,再画输入区边栏和文本内容;如果文本为空就画灰色占位文字;最后画提示行 → 出来是缓冲区被填上该显示的字符。

调用关系:外层界面刷新时调用它。它会使用 feedback_title_and_placeholder 取文案,用 TextArea 的渲染能力画输入内容,并用 standard_popup_hint_line 画统一的弹窗提示。

调用图:调用 5 个内部函数(input_height, intro_lines, feedback_title_and_placeholder, standard_popup_hint_line, text);被 1 处调用(render);外部调用 4 个(from, new, render_ref, vec!)。

FeedbackNoteView::input_height253–257 ↗
fn input_height(&self, width: u16) -> u16

作用:计算备注输入区域应该多高。它让输入框随内容增高,但最多只占有限高度,避免把屏幕撑爆。

数据流:进去的是窗口宽度 → 它扣掉左侧边栏宽度,询问文本框在这个宽度下需要几行,再限制在 1 到 8 行之间,并加上一行空白/间隔 → 出来是最终输入区域高度,最多 9 行。

调用关系:desired_height、cursor_pos 和 render 都会调用它。它把 TextArea 的自然高度转成这个反馈窗口能接受的高度。

调用图:调用 1 个内部函数(desired_height);被 3 处调用(cursor_pos, desired_height, render)。

FeedbackNoteView::intro_lines259–262 ↗
fn intro_lines(&self, _width: u16) -> Vec<Line<'static>>

作用:生成反馈窗口顶部的介绍行。现在主要是一行带分类的标题。

数据流:进去的是宽度参数和当前反馈分类 → 它根据分类拿到标题,把左侧彩色标记和加粗标题拼成一行 → 出来是一组要画在顶部的 Line。

调用关系:desired_height、cursor_pos 和 render 都依赖它来保持顶部高度一致。它通过 feedback_title_and_placeholder 拿到分类对应文案。

调用图:调用 1 个内部函数(feedback_title_and_placeholder);被 3 处调用(cursor_pos, desired_height, render);外部调用 1 个(vec!)。

should_show_feedback_connectivity_details265–270 ↗
fn should_show_feedback_connectivity_details(
    category: FeedbackCategory,
    diagnostics: &FeedbackDiagnostics,
) -> bool

作用:判断是否应该在上传日志确认框里显示网络连接诊断细节。它避免在好评里展示没必要的排查信息。

数据流:进去的是反馈分类和诊断信息 → 它检查分类是不是 GoodResult,以及诊断信息是否为空 → 出来是布尔值:问题类反馈且有诊断时才返回 true。

调用关系:feedback_upload_consent_params 在拼确认弹窗内容时调用它。这样诊断细节只在真正有助于排查问题时出现。

调用图:调用 1 个内部函数(is_empty);被 1 处调用(feedback_upload_consent_params)。

gutter272–274 ↗
fn gutter() -> Span<'static>

作用:生成反馈窗口左边那条青色的视觉标记。它像便签纸左侧的彩色边,让这个区域更容易被看出来。

数据流:没有输入 → 它把“▌ ”这两个字符加上青色样式 → 出来是一个 Span,也就是带样式的一小段文字。

调用关系:intro_lines 和 render 会用它画标题和输入区左侧。它只是一个小的界面装饰工具。

feedback_title_and_placeholder276–299 ↗
fn feedback_title_and_placeholder(category: FeedbackCategory) -> (String, String)

作用:根据反馈类型给出标题和输入框里的灰色提示语。比如 bug、差结果、好结果会显示不同标题。

数据流:进去的是 FeedbackCategory → 它匹配具体分类,选出对应的标题和占位说明 → 出来是一对字符串:标题和占位文字。

调用关系:intro_lines 用它画标题,render 用它在输入框为空时显示提示。它集中保存这些用户能看到的反馈文案。

调用图:被 2 处调用(intro_lines, render)。

feedback_classification301–309 ↗
fn feedback_classification(category: FeedbackCategory) -> &'static str

作用:把反馈分类转换成稳定的英文标识。这个标识适合给日志、接口或统计系统使用。

数据流:进去的是反馈分类 → 它把 BadResult、GoodResult、Bug 等枚举值映射成 bad_result、good_result、bug 等字符串 → 出来是一个静态英文标签。

调用关系:它是反馈分类的通用翻译器。虽然这个文件片段里没有展示调用者,但上传或记录反馈时可以用它得到机器更容易处理的分类名。

feedback_success_cell311–363 ↗
fn feedback_success_cell(
    category: FeedbackCategory,
    include_logs: bool,
    thread_id: &str,
    feedback_audience: FeedbackAudience,
) -> history_cell::WebHyperlinkHistoryCell

作用:生成“反馈已提交”的聊天历史提示块。它告诉用户反馈有没有带日志、线程 ID 是什么,以及是否需要去 GitHub 或内部链接继续补充。

数据流:进去的是反馈分类、是否上传日志、thread_id 和反馈受众 → 它先选一句成功提示,再用 issue_url_for_category 决定是否附后续链接,最后拼成多行可点击样式文本 → 出来是一个 WebHyperlinkHistoryCell,用来显示在历史记录里。

调用关系:反馈上传完成后,应用会用它把结果展示给用户。它把链接判断交给 issue_url_for_category,把最终显示交给 history_cell。

调用图:调用 2 个内部函数(issue_url_for_category, new);被 4 处调用(feedback_success_cell_matches_employee_bug_copy, feedback_success_cell_matches_external_bug_copy, feedback_success_cell_matches_good_result_copy, feedback_success_cell_uses_issue_links_for_remaining_categories);外部调用 2 个(from, vec!)。

issue_url_for_category365–385 ↗
fn issue_url_for_category(
    category: FeedbackCategory,
    thread_id: &str,
    feedback_audience: FeedbackAudience,
) -> Option<String>

作用:决定某类反馈是否需要后续链接,以及链接指向哪里。问题类反馈有链接,好评没有。

数据流:进去的是反馈分类、线程 ID 和用户受众 → 对 bug、差结果、安全检查、其他反馈,它给员工生成内部链接,给外部用户生成带 thread_id 的 GitHub issue 地址;对好评返回 None → 出来是可选 URL。

调用关系:feedback_success_cell 调用它来决定成功提示里放什么。测试也直接检查它,防止内部和外部链接规则被改错。

调用图:调用 1 个内部函数(slack_feedback_url);被 2 处调用(feedback_success_cell, issue_url_available_for_bug_bad_result_safety_check_and_other);外部调用 1 个(format!)。

slack_feedback_url391–393 ↗
fn slack_feedback_url(_thread_id: &str) -> String

作用:生成 OpenAI 员工使用的内部反馈入口地址。现在它返回一个固定的内部 go 链接。

数据流:进去的是 thread_id,但当前没有使用它 → 它返回 CODEX_FEEDBACK_INTERNAL_URL 的字符串 → 出来是内部反馈链接。

调用关系:issue_url_for_category 在受众是 OpenAiEmployee 时调用它。保留 thread_id 参数是为了和外部链接生成路径形状一致,方便以后扩展。

调用图:被 1 处调用(issue_url_for_category)。

feedback_selection_params396–435 ↗
fn feedback_selection_params(
    app_event_tx: AppEventSender,
) -> super::SelectionViewParams

作用:构造“这次体验怎么样?”的反馈分类选择弹窗。用户会在这里选 bug、差结果、好结果、安全检查或其他。

数据流:进去的是应用消息发送器 → 它创建一个带标题的选择菜单,并为每个分类创建一个选项 → 出来是 SelectionViewParams,外层界面可以直接用它显示弹窗。

调用关系:当用户触发反馈入口时会用到它。它通过 make_feedback_item 给每个选项绑定动作:选中后发出 OpenFeedbackConsent 事件。

调用图:外部调用 2 个(default, vec!)。

feedback_disabled_params438–450 ↗
fn feedback_disabled_params() -> super::SelectionViewParams

作用:构造“反馈被配置禁用”的提示弹窗。它不让用户继续提交,只提供关闭选项。

数据流:没有业务输入 → 它创建带标题、副标题、底部提示和一个 Close 项的选择菜单 → 出来是 SelectionViewParams。

调用关系:当配置关闭反馈功能时,应用会显示这个弹窗而不是正常反馈菜单。它使用 standard_popup_hint_line 保持快捷键提示风格统一。

调用图:调用 1 个内部函数(standard_popup_hint_line);外部调用 2 个(default, vec!)。

make_feedback_item452–468 ↗
fn make_feedback_item(
    app_event_tx: AppEventSender,
    name: &str,
    description: &str,
    category: FeedbackCategory,
) -> super::SelectionItem

作用:创建反馈分类菜单里的单个选项。每个选项都有名字、说明,以及选中后要触发的动作。

数据流:进去的是消息发送器、选项名、说明文字和分类 → 它把这些包装成 SelectionItem,并绑定一个动作:发送 OpenFeedbackConsent 事件 → 出来是一个可放进选择菜单的条目。

调用关系:feedback_selection_params 连续调用它生成五个分类项。它把用户点击菜单这件事转成应用内部事件。

调用图:外部调用 3 个(new, default, vec!)。

tests::render587–593 ↗
fn render(view: &FeedbackNoteView, width: u16) -> String

作用:测试用的小帮手:把 FeedbackNoteView 渲染成普通字符串,方便和快照比对。

数据流:进去的是一个反馈备注视图和宽度 → 它先问视图想要多高,再创建空 Buffer,让视图画进去,最后转成文本 → 出来是一段可比较的字符串。

调用关系:多个 feedback_view_* 快照测试会调用它。它把 desired_height、render 和 render_buffer 串起来。

调用图:调用 2 个内部函数(desired_height, render);外部调用 3 个(empty, new, render_buffer)。

tests::render_renderable595–601 ↗
fn render_renderable(renderable: &dyn Renderable, width: u16) -> String

作用:测试用的小帮手:把任意 Renderable(一种“能画到终端上的东西”)渲染成字符串。

数据流:进去的是一个可渲染对象和宽度 → 它计算高度、准备缓冲区、调用对象自己的 render 方法,再把缓冲区转成文本 → 出来是测试可断言的字符串。

调用关系:上传日志确认弹窗的测试用它检查 header 内容。它和 tests::render 类似,但不限定对象必须是 FeedbackNoteView。

调用图:外部调用 5 个(empty, new, render_buffer, desired_height, render)。

tests::render_buffer603–626 ↗
fn render_buffer(area: Rect, buf: &Buffer) -> String

作用:把终端绘图缓冲区转成普通多行文本。这样测试不用理解颜色和样式,只看字符内容。

数据流:进去的是屏幕矩形和 Buffer → 它逐行逐列读出字符,空字符补成空格,去掉每行末尾空白,再去掉整体首尾空白行 → 出来是干净的多行字符串。

调用关系:tests::render 和 tests::render_renderable 都会把最终转换工作交给它。它是快照测试和界面渲染之间的翻译器。

tests::render_cell628–641 ↗
fn render_cell(cell: &impl history_cell::HistoryCell, width: u16) -> String

作用:把历史消息单元格渲染成普通字符串,方便检查成功提示文案是否正确。

数据流:进去的是一个 HistoryCell 和宽度 → 它调用 display_lines 得到显示行,抽出每个 span 的文字并拼起来 → 出来是一段多行字符串。

调用关系:反馈成功提示相关测试会调用它。它把带样式、可能带链接的历史单元格简化成纯文本来比对。

调用图:外部调用 1 个(display_lines)。

tests::make_view643–649 ↗
fn make_view(category: FeedbackCategory) -> FeedbackNoteView

作用:测试用的小工厂:快速造一个指定分类的 FeedbackNoteView。

数据流:进去的是反馈分类 → 它创建一个测试用的消息通道和 AppEventSender,再调用 FeedbackNoteView::new → 出来是默认带日志、没有 turn_id 的反馈备注视图。

调用关系:多个界面快照测试用它减少重复代码。它是测试侧创建反馈输入框的统一入口。

调用图:调用 2 个内部函数(new, new)。

tests::feedback_view_bad_result652–656 ↗
fn feedback_view_bad_result()

作用:检查“差结果”反馈输入框长什么样。它用快照防止标题、占位提示或布局被意外改坏。

数据流:进去没有外部输入 → 它创建 BadResult 视图,渲染成 60 宽的文本,再和保存的快照比较 → 出来是测试通过或失败。

调用关系:它调用 make_view 和 tests::render。属于反馈备注窗口的一组分类外观测试之一。

调用图:外部调用 3 个(assert_snapshot!, make_view, render)。

tests::feedback_view_good_result659–663 ↗
fn feedback_view_good_result()

作用:检查“好结果”反馈输入框的显示效果。重点是确认好评分类的标题和提示文案正确。

数据流:进去没有外部输入 → 它创建 GoodResult 视图,渲染后做快照比对 → 出来是测试结果。

调用关系:它调用 make_view 和 tests::render。和其他分类快照测试一起保护反馈 UI 文案。

调用图:外部调用 3 个(assert_snapshot!, make_view, render)。

tests::feedback_view_bug666–670 ↗
fn feedback_view_bug()

作用:检查“bug”反馈输入框的界面快照。这样 UI 改动会被测试及时发现。

数据流:进去没有外部输入 → 它创建 Bug 视图并渲染为文本 → 输出给快照断言比较。

调用关系:它调用 make_view 和 tests::render,是反馈分类视图快照测试的一部分。

调用图:外部调用 3 个(assert_snapshot!, make_view, render)。

tests::feedback_view_other673–677 ↗
fn feedback_view_other()

作用:检查“其他”反馈输入框的界面显示。它保证兜底类反馈也有正确标题和输入提示。

数据流:进去没有外部输入 → 它创建 Other 视图,渲染后与快照比较 → 出来是通过或失败。

调用关系:它调用 make_view 和 tests::render。用于覆盖所有反馈分类。

调用图:外部调用 3 个(assert_snapshot!, make_view, render)。

tests::feedback_view_safety_check680–684 ↗
fn feedback_view_safety_check()

作用:检查“安全检查”反馈输入框的界面显示。这个分类的占位提示更具体,所以需要单独保护。

数据流:进去没有外部输入 → 它创建 SafetyCheck 视图,渲染成字符串并做快照断言 → 出来是测试结果。

调用关系:它调用 make_view 和 tests::render。确保安全检查类反馈不会误用普通提示语。

调用图:外部调用 3 个(assert_snapshot!, make_view, render)。

tests::feedback_view_with_connectivity_diagnostics687–699 ↗
fn feedback_view_with_connectivity_diagnostics()

作用:检查一个不带日志的 bug 反馈备注视图渲染结果。名字提到连接诊断,但这里实际渲染的是备注输入窗口本身。

数据流:进去没有外部输入 → 它创建测试通道和 Bug 类 FeedbackNoteView,include_logs 设为 false,然后渲染并比对快照 → 出来是测试结果。

调用关系:它直接调用 FeedbackNoteView::new 和 tests::render。用于覆盖 include_logs 为 false 时输入框仍能正常显示。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert_snapshot!, render)。

tests::submit_feedback_emits_submit_event_with_trimmed_note741–765 ↗
fn submit_feedback_emits_submit_event_with_trimmed_note()

作用:检查提交反馈时会发出正确事件,并且备注前后的空格会被去掉。

数据流:进去没有外部输入 → 它创建反馈视图,往文本框插入带空格的文字,调用 submit,然后从测试消息通道取出事件检查字段 → 出来是断言通过,同时视图应被标记完成。

调用关系:它直接使用 FeedbackNoteView::new 和 submit。这个测试保护提交事件的核心行为。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert!, assert_eq!)。

tests::submit_feedback_omits_empty_note768–790 ↗
fn submit_feedback_omits_empty_note()

作用:检查空备注不会被当成一段空字符串提交,而是按“没有备注”处理。

数据流:进去没有外部输入 → 它创建 GoodResult 视图,不输入任何内容就提交,从通道读取 SubmitFeedback 事件 → 出来是 reason 为 None 的断言结果。

调用关系:它直接覆盖 FeedbackNoteView::submit 的空输入分支,确保后台不会收到无意义的空备注。

调用图:调用 2 个内部函数(new, new);外部调用 1 个(assert!)。

tests::should_show_feedback_connectivity_details_only_for_non_good_result_with_diagnostics793–815 ↗
fn should_show_feedback_connectivity_details_only_for_non_good_result_with_diagnostics()

作用:检查网络诊断细节只在“有诊断且不是好评”时显示。

数据流:进去没有外部输入 → 它构造一条代理相关诊断,分别测试 Bug、GoodResult 和无诊断的 BadResult → 出来是三个布尔断言结果。

调用关系:它直接验证 should_show_feedback_connectivity_details 的规则,防止好评里出现排障信息,也防止没有诊断时显示空标题。

调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, vec!)。

tests::issue_url_available_for_bug_bad_result_safety_check_and_other818–860 ↗
fn issue_url_available_for_bug_bad_result_safety_check_and_other()

作用:检查哪些反馈分类会生成后续链接,以及内部用户和外部用户的链接是否不同。

数据流:进去没有外部输入 → 它对 bug、差结果、其他、安全检查和好评分别调用 issue_url_for_category,并检查内部 go 链接、外部 GitHub 链接和好评无链接的规则 → 出来是断言结果。

调用关系:它直接测试 issue_url_for_category,也间接保护 slack_feedback_url 和外部 issue 地址格式。

调用图:调用 1 个内部函数(issue_url_for_category);外部调用 2 个(assert!, assert_eq!)。

tests::feedback_success_cell_matches_external_bug_copy863–877 ↗
fn feedback_success_cell_matches_external_bug_copy()

作用:检查外部用户提交 bug 且上传日志后,成功提示文案和 GitHub 链接是否正确。

数据流:进去没有外部输入 → 它调用 feedback_success_cell 生成历史提示,再用 render_cell 转成纯文本 → 出来是和预期整段文字的比较结果。

调用关系:它覆盖 feedback_success_cell 的外部问题反馈路径,确保用户能看到开 issue 的地址和 thread ID。

调用图:调用 1 个内部函数(feedback_success_cell);外部调用 2 个(assert_eq!, render_cell)。

tests::feedback_success_cell_matches_employee_bug_copy880–894 ↗
fn feedback_success_cell_matches_employee_bug_copy()

作用:检查 OpenAI 员工提交 bug 后看到的是内部反馈指引,而不是外部 GitHub 指引。

数据流:进去没有外部输入 → 它生成员工受众的 bug 成功提示,转成文本,和预期内部链接及补充信息提示比较 → 出来是断言结果。

调用关系:它覆盖 feedback_success_cell 的员工路径,并间接检查 issue_url_for_category 选择了内部链接。

调用图:调用 1 个内部函数(feedback_success_cell);外部调用 2 个(assert_eq!, render_cell)。

tests::feedback_success_cell_matches_good_result_copy897–911 ↗
fn feedback_success_cell_matches_good_result_copy()

作用:检查好评提交成功后的文案。好评不需要开 issue,只感谢用户并显示线程 ID。

数据流:进去没有外部输入 → 它用 GoodResult 和 include_logs=false 生成成功提示,再转成纯文本比较 → 出来是断言结果。

调用关系:它覆盖 feedback_success_cell 在没有后续链接时的分支,确保好评流程更轻量。

调用图:调用 1 个内部函数(feedback_success_cell);外部调用 2 个(assert_eq!, render_cell)。

底部窗格请求和外部操作覆盖层

这些文件实现用于应用链接流程,以及位于持久 composer 上方、由服务器驱动的 elicitation 或 request-user-input 交互的专用覆盖层。

tui/src/bottom_pane/mcp_server_elicitation.rs源码 ↗
domain_logicrequest handling

可以把这个文件想成前台服务员。后台 MCP 服务器递来一张纸,上面可能写着“允许这个工具运行吗?”也可能写着“请填这些字段”。这里先读懂这张纸,识别普通表单、工具调用审批、工具安装建议等情况;再生成终端界面,支持文字输入、单选、快捷键切换字段、取消、粘贴和校验必填项。用户按回车后,它会把答案整理成 JSON(一种常见的数据格式,像键值表),或整理成“允许/拒绝/取消”的决定,通过 AppEventSender 发回主程序。它还会排队处理多个请求,避免多个弹窗抢用户注意力。

函数细节105
ComposerDraft::text_with_pending86–100 ↗
fn text_with_pending(&self) -> String

作用:把输入框里已经写好的文字,和还没完全展开的粘贴内容合成一份最终文字。别人要读取用户答案时会用它,避免漏掉刚粘贴但还在等待处理的内容。

数据流:进去的是草稿里的普通文字、文字元素和待处理粘贴列表;它如果没有待处理粘贴就直接返回文字,如果有就请 ChatComposer 展开这些粘贴;出来的是包含粘贴内容的完整文本,不修改草稿本身。

调用关系:它在 field_value 取文字答案时发挥作用,属于提交前最后确认文本内容的小帮手;具体展开工作交给 ChatComposer::expand_pending_pastes。

调用图:调用 1 个内部函数(expand_pending_pastes);外部调用 1 个(debug_assert!)。

FooterTip::new191–196 ↗
fn new(text: impl Into<String>) -> Self

作用:创建一条普通的页脚提示,比如“esc to cancel”。它让界面底部能统一显示操作说明。

数据流:进去的是一段可转成字符串的文字;它包装成 FooterTip,并把 highlight 设为 false;出来的是普通样式提示。

调用关系:footer_tips 和 render_footer 会用它组装提示栏;高亮提示则由 FooterTip::highlighted 创建。

调用图:被 4 处调用(footer_tips, render_footer, footer_tips, render_ui_at);外部调用 1 个(into)。

FooterTip::highlighted198–203 ↗
fn highlighted(text: impl Into<String>) -> Self

作用:创建一条需要强调的页脚提示,比如错误信息或“enter to submit”。它让重要操作在界面上更醒目。

数据流:进去的是提示文字;它包装成 FooterTip,并把 highlight 设为 true;出来的是会被渲染成醒目样式的提示。

调用关系:footer_tip_lines 和 footer_tips 会用它标出当前最重要的信息,最后由 render_footer 画出来。

调用图:被 3 处调用(footer_tip_lines, footer_tips, footer_tips);外部调用 1 个(into)。

McpServerElicitationFormRequest::from_app_server_request207–235 ↗
fn from_app_server_request(
        thread_id: ThreadId,
        request_id: AppServerRequestId,
        request: McpServerElicitationRequestParams,
    ) -> Option<Self>

作用:把应用服务器发来的 MCP 提问请求,转换成这个界面能使用的表单请求。它是外部协议进入这个文件的主要入口。

数据流:进去的是线程编号、请求编号和服务器请求参数;它只接受 Form 类型请求,并把里面的 schema 转成通用 JSON;出来是 McpServerElicitationFormRequest,不能支持时返回 None。

调用关系:上层在收到 MCP elicitation 请求时调用它;它把更细的判断交给 from_parts,测试里的辅助函数也通过它构造请求。

调用图:被 4 处调用(interactive_request_for_thread_request, from_form_request, resolved_request_dismisses_overlay_without_emitting_events, handle_elicitation_request_now);外部调用 2 个(from_parts, to_value)。

McpServerElicitationFormRequest::from_parts237–354 ↗
fn from_parts(
        thread_id: ThreadId,
        server_name: String,
        request_id: AppServerRequestId,
        meta: Option<Value>,
        message: String,
        requested_schema: Value,

作用:根据请求内容判断该显示普通表单、审批按钮,还是工具建议信息。它决定用户看到什么、提交后按什么方式回应。

数据流:进去的是线程、服务器名、请求编号、元数据、提示语和 schema;它解析工具建议、持久允许选项、工具参数摘要和字段定义;出来是一份完整表单请求,无法识别字段时返回 None。

调用关系:它由 from_app_server_request 调用,是请求解析的核心;会调用 parse_fields_from_schema、parse_tool_suggestion_request、parse_tool_approval_display_params 和 approval_supports_persist_mode。

调用图:调用 4 个内部函数(approval_supports_persist_mode, parse_fields_from_schema, parse_tool_approval_display_params, parse_tool_suggestion_request);外部调用 5 个(String, as_object, is_null, new, vec!)。

McpServerElicitationFormRequest::tool_suggestion356–358 ↗
fn tool_suggestion(&self) -> Option<&ToolSuggestionRequest>

作用:取出这个请求里可能带着的工具建议,比如建议安装某个 connector 或 plugin。上层用它决定是否展示特殊的工具建议界面或入口。

数据流:进去的是表单请求自身;它读取内部的 tool_suggestion 字段;出来是可选的 ToolSuggestionRequest 引用,不改动数据。

调用关系:push_mcp_server_elicitation_request 会读取它,判断当前提问是否还带有工具推荐信息。

调用图:被 1 处调用(push_mcp_server_elicitation_request)。

McpServerElicitationFormRequest::thread_id360–362 ↗
fn thread_id(&self) -> ThreadId

作用:返回这次提问属于哪个对话线程。这样回答才能送回正确的聊天上下文。

数据流:进去的是表单请求自身;它读取 thread_id;出来是线程编号的副本。

调用关系:push_mcp_server_elicitation_request 会用它把请求放到正确线程相关的 UI 流程中。

调用图:被 1 处调用(push_mcp_server_elicitation_request)。

McpServerElicitationFormRequest::server_name364–366 ↗
fn server_name(&self) -> &str

作用:返回发起提问的 MCP 服务器名字。回答时需要这个名字来告诉后台“回复的是哪台服务器”。

数据流:进去的是表单请求自身;它读取 server_name;出来是服务器名字符串引用。

调用关系:push_mcp_server_elicitation_request 会读取它,用来识别和展示请求来源。

调用图:被 1 处调用(push_mcp_server_elicitation_request)。

McpServerElicitationFormRequest::request_id368–370 ↗
fn request_id(&self) -> &AppServerRequestId

作用:返回这次提问的唯一请求编号。它像快递单号,确保回答不会送错。

数据流:进去的是表单请求自身;它读取 request_id;出来是请求编号引用。

调用关系:push_mcp_server_elicitation_request 会用它登记请求,后续提交或撤销时也靠这个编号对应。

调用图:被 1 处调用(push_mcp_server_elicitation_request)。

parse_tool_suggestion_request373–409 ↗
fn parse_tool_suggestion_request(meta: Option<&Value>) -> Option<ToolSuggestionRequest>

作用:从元数据里识别“工具建议”请求,例如建议安装或启用某个工具。它把散落的 JSON 字段整理成更好用的结构。

数据流:进去的是可选 JSON 元数据;它检查 kind、工具类型、建议类型、原因、工具编号、工具名和安装地址;出来是 ToolSuggestionRequest,字段缺失或不合法就返回 None。

调用关系:from_parts 会先调用它,判断这次提问是不是工具建议;解析成功后保存在 McpServerElicitationFormRequest 里给上层查看。

调用图:被 1 处调用(from_parts)。

approval_supports_persist_mode411–427 ↗
fn approval_supports_persist_mode(meta: Option<&Value>, expected_mode: &str) -> bool

作用:判断这次审批是否允许“记住选择”,比如本次会话一直允许或以后总是允许。这样界面才不会显示后台不支持的选项。

数据流:进去的是元数据和期望的记住模式;它读取 persist 字段,兼容单个字符串和字符串数组;出来是 true 或 false。

调用关系:from_parts 在生成审批选项时调用它,决定是否加入“Allow for this session”和“Always allow”。

调用图:被 1 处调用(from_parts)。

parse_tool_approval_display_params429–464 ↗
fn parse_tool_approval_display_params(meta: Option<&Value>) -> Vec<McpToolApprovalDisplayParam>

作用:整理工具审批时要展示给用户看的参数摘要,比如日历名、事件标题。这样用户批准前能看清工具打算做什么。

数据流:进去的是可选元数据;它优先读取专门的显示参数列表,读不到时退回到原始工具参数并按名字排序;出来是一组可展示参数。

调用关系:from_parts 在工具调用审批且没有表单字段时调用它;单个显示项的解析由 parse_tool_approval_display_param 完成。

调用图:被 1 处调用(from_parts);外部调用 1 个(new)。

parse_tool_approval_display_param466–485 ↗
fn parse_tool_approval_display_param(value: &Value) -> Option<McpToolApprovalDisplayParam>

作用:解析一个工具参数的显示条目。它负责过滤掉没有名字、显示名为空或缺少值的坏数据。

数据流:进去的是一个 JSON 值;它要求这个值是对象,并取 name、display_name 和 value;出来是 McpToolApprovalDisplayParam,格式不对就返回 None。

调用关系:parse_tool_approval_display_params 在读取显式显示列表时逐项使用它。

调用图:外部调用 2 个(as_object, get)。

format_tool_approval_display_message487–511 ↗
fn format_tool_approval_display_message(
    message: &str,
    approval_display_params: &[McpToolApprovalDisplayParam],
) -> String

作用:把工具审批消息和少量关键参数合并成用户能读的提示文本。它避免审批框只说“允许吗”却不告诉用户工具参数。

数据流:进去的是原始消息和参数列表;它去掉消息两端空白,最多取前几个参数格式化并拼接;出来是一段带换行的显示文本。

调用关系:current_prompt_text 会调用它生成表单顶部提示;每行参数由 format_tool_approval_display_param_line 处理。

调用图:被 1 处调用(current_prompt_text);外部调用 3 个(new, is_empty, iter)。

format_tool_approval_display_param_line513–519 ↗
fn format_tool_approval_display_param_line(param: &McpToolApprovalDisplayParam) -> String

作用:把一个工具参数格式化成“显示名: 值”的一行文字。它让参数摘要整齐易读。

数据流:进去的是一个显示参数;它取 display_name 和格式化后的 value 拼成字符串;出来是一行文本。

调用关系:format_tool_approval_display_message 会对每个要展示的参数调用它,值的压缩和截断交给 format_tool_approval_display_param_value。

调用图:外部调用 1 个(format!)。

format_tool_approval_display_param_value521–530 ↗
fn format_tool_approval_display_param_value(value: &Value) -> String

作用:把参数值变成适合终端展示的短文本。它会压缩 JSON、去掉字符串里多余空白,并截断太长的内容。

数据流:进去的是 JSON 值;字符串会按空白压成一行,其他值会转成紧凑 JSON;出来是最多固定长度的文本。

调用关系:format_tool_approval_display_param_line 调用它,内部借助 format_json_compact 和 truncate_text 保持显示简洁。

调用图:调用 2 个内部函数(format_json_compact, truncate_text);外部调用 1 个(to_string)。

parse_fields_from_schema532–557 ↗
fn parse_fields_from_schema(requested_schema: &Value) -> Option<Vec<McpServerElicitationField>>

作用:把服务器给的 schema(一份描述表单字段的规则)转换成界面能渲染的字段列表。它决定哪些字段可输入、哪些必填。

数据流:进去的是 requested_schema JSON;它要求 schema 是 object,读取 required 和 properties,再逐个解析字段;出来是字段列表,遇到不支持或空字段返回 None。

调用关系:from_parts 在普通表单模式下调用它;单个字段交给 parse_field 识别。

调用图:调用 1 个内部函数(parse_field);被 1 处调用(from_parts);外部调用 2 个(as_object, new)。

parse_field559–640 ↗
fn parse_field(
    id: &str,
    property: McpElicitationPrimitiveSchema,
    required: bool,
) -> Option<McpServerElicitationField>

作用:把一个 schema 字段变成具体输入控件,比如文本框、真假选择、枚举单选。它只接受当前界面支持的类型。

数据流:进去的是字段 id、字段 schema 和是否必填;它按字符串、布尔值、旧枚举、单选枚举分别构造字段;出来是 McpServerElicitationField,不支持数字或多选就返回 None。

调用关系:parse_fields_from_schema 对每个属性调用它;新式单选枚举会继续交给 parse_single_select_field。

调用图:调用 1 个内部函数(parse_single_select_field);被 1 处调用(parse_fields_from_schema)。

parse_single_select_field642–705 ↗
fn parse_single_select_field(
    id: &str,
    schema: McpElicitationSingleSelectEnumSchema,
    required: bool,
) -> Option<McpServerElicitationField>

作用:解析单选枚举字段,也就是从一组选项里选一个。它兼容有标题和没标题两种 schema 写法。

数据流:进去的是字段 id、单选枚举 schema 和必填标记;它读取标题、说明、默认值和选项;出来是带 Select 输入的字段。

调用关系:parse_field 碰到 SingleSelect 枚举时调用它,结果再进入表单渲染和提交流程。

调用图:被 1 处调用(parse_field)。

McpServerElicitationOverlay::new721–736 ↗
fn new(
        request: McpServerElicitationFormRequest,
        app_event_tx: AppEventSender,
        has_input_focus: bool,
        enhanced_keys_supported: bool,
        disable_paste_burst: bool,

作用:创建一个新的 MCP 提问弹窗,主要给测试使用。它用默认按键配置包装 new_with_keymap。

数据流:进去的是请求、事件发送器、输入焦点和键盘能力等设置;它补上默认列表按键映射;出来是初始化好的 Overlay。

调用关系:多个测试直接调用它;正式路径通常会调用 new_with_keymap 以使用运行时按键配置。

调用图:调用 1 个内部函数(defaults);被 13 处调用(approval_form_tool_approval_snapshot, approval_form_tool_approval_with_param_summary_snapshot, approval_form_tool_approval_with_persist_options_snapshot, boolean_form_snapshot, ctrl_c_cancels_elicitation, empty_tool_approval_schema_always_allow_sets_persist_meta, empty_tool_approval_schema_session_choice_sets_persist_meta, horizontal_list_keys_move_between_select_fields, message_only_form_snapshot, message_only_form_with_persist_options_snapshot (+3 more));外部调用 1 个(new_with_keymap)。

McpServerElicitationOverlay::new_with_keymap738–769 ↗
fn new_with_keymap(
        request: McpServerElicitationFormRequest,
        app_event_tx: AppEventSender,
        has_input_focus: bool,
        enhanced_keys_supported: bool,
        disable_paste_

作用:创建真正可用的提问弹窗,并把输入框、状态和按键规则准备好。没有这一步,界面无法保存答案或响应按键。

数据流:进去的是表单请求、事件发送器、焦点设置、粘贴设置和列表按键配置;它创建 ChatComposer 输入框,初始化队列和答案状态;出来是已定位到第一个字段的 Overlay。

调用关系:push_mcp_server_elicitation_request 会用它打开新弹窗;初始化内部会调用 reset_for_request 和 restore_current_draft。

调用图:调用 2 个内部函数(new_with_config, plain_text);被 1 处调用(push_mcp_server_elicitation_request);外部调用 3 个(new, new, clone)。

McpServerElicitationOverlay::reset_for_request771–798 ↗
fn reset_for_request(&mut self)

作用:在开始处理一个新请求时,清空旧答案并按字段创建新的答题状态。它保证上一个请求的内容不会串到下一个请求。

数据流:进去的是当前 Overlay 和其中的新 request;它为每个字段设置选择状态、默认选项和是否已提交;出来是重置后的答案列表、当前字段和输入框。

调用关系:new_with_keymap 首次初始化会用它,advance_queue_or_complete 切到排队请求时也会用它。

调用图:调用 1 个内部函数(set_text_content);被 1 处调用(advance_queue_or_complete);外部调用 2 个(new, new)。

McpServerElicitationOverlay::field_count800–802 ↗
fn field_count(&self) -> usize

作用:返回当前表单有多少个字段。界面导航、进度显示和提交判断都要用这个数字。

数据流:进去的是 Overlay;它读取 request.fields 的长度;出来是字段数量。

调用关系:footer_tips、move_field、go_next_or_submit、jump_to_field 和 render 都靠它判断当前位置和是否到最后。

调用图:被 5 处调用(footer_tips, go_next_or_submit, jump_to_field, move_field, render)。

McpServerElicitationOverlay::current_index804–806 ↗
fn current_index(&self) -> usize

作用:返回当前正在回答第几个字段。它是所有“当前字段”相关操作的坐标。

数据流:进去的是 Overlay;它读取 current_idx;出来是当前字段下标。

调用关系:current_field、current_answer、footer_tips、render 等函数通过它定位当前内容。

调用图:被 6 处调用(current_answer, current_field, footer_tips, go_next_or_submit, is_current_field_answered, render)。

McpServerElicitationOverlay::current_field808–810 ↗
fn current_field(&self) -> Option<&McpServerElicitationField>

作用:取出当前正在显示的字段定义。渲染提示、判断输入类型和生成占位文字都依赖它。

数据流:进去的是 Overlay;它用 current_index 到 request.fields 里查找;出来是可选字段引用。

调用关系:answer_placeholder、current_options 和 current_prompt_text 会调用它;如果没有字段,调用方会用安全的默认行为。

调用图:调用 1 个内部函数(current_index);被 3 处调用(answer_placeholder, current_options, current_prompt_text)。

McpServerElicitationOverlay::current_answer812–814 ↗
fn current_answer(&self) -> Option<&McpServerElicitationAnswerState>

作用:取出当前字段对应的答案状态。它告诉界面当前选中了什么、草稿是什么、是否已经确认。

数据流:进去的是 Overlay;它用 current_index 到 answers 里查找;出来是可选答案状态引用。

调用关系:restore_current_draft、selected_option_index、options_required_height 和 render_input 会读取它。

调用图:调用 1 个内部函数(current_index);被 4 处调用(options_required_height, render_input, restore_current_draft, selected_option_index)。

McpServerElicitationOverlay::current_answer_mut816–819 ↗
fn current_answer_mut(&mut self) -> Option<&mut McpServerElicitationAnswerState>

作用:可修改地取出当前字段答案。用户输入、选择、清空时都靠它改状态。

数据流:进去的是可变 Overlay;它用 current_idx 找到 answers 里的当前项;出来是可修改答案引用。

调用关系:save_current_draft、select_current_option、clear_selection、handle_key_event、handle_paste 和 apply_submission_to_draft 都会用它更新答案。

调用图:被 7 处调用(apply_submission_to_draft, clear_current_draft, clear_selection, handle_key_event, handle_paste, save_current_draft, select_current_option)。

McpServerElicitationOverlay::capture_composer_draft821–833 ↗
fn capture_composer_draft(&self) -> ComposerDraft

作用:把当前输入框内容拍一张“快照”。切换字段或判断内容是否变化时需要保存这份草稿。

数据流:进去的是 Overlay;它读取 ChatComposer 的文字、文字元素、本地图片路径和待处理粘贴;出来是 ComposerDraft。

调用关系:save_current_draft 用它保存当前字段,handle_key_event 用它比较按键前后是否改了内容。

调用图:调用 4 个内部函数(current_text, local_images, pending_pastes, text_elements);被 2 处调用(handle_key_event, save_current_draft)。

McpServerElicitationOverlay::restore_current_draft835–856 ↗
fn restore_current_draft(&mut self)

作用:把当前字段之前保存的草稿放回输入框。这样用户在多个字段之间切换时,已输入的内容不会丢。

数据流:进去的是可变 Overlay;它设置占位文字和页脚提示,选择题清空输入框,文本题恢复草稿和待处理粘贴;出来是输入框显示当前字段内容。

调用关系:new_with_keymap、move_field、jump_to_field 和 advance_queue_or_complete 都会在切换上下文后调用它。

调用图:调用 8 个内部函数(move_cursor_to_end, set_footer_hint_override, set_pending_pastes, set_placeholder_text, set_text_content, answer_placeholder, current_answer, current_field_is_select);被 3 处调用(advance_queue_or_complete, jump_to_field, move_field);外部调用 2 个(new, new)。

McpServerElicitationOverlay::save_current_draft858–869 ↗
fn save_current_draft(&mut self)

作用:保存当前文本输入框的草稿。它防止用户切到别的字段后,刚打的字消失。

数据流:进去的是可变 Overlay;如果当前是选择题就不处理,文本题则抓取输入框快照并写入答案状态;如果已提交答案被改动,会标成未提交。

调用关系:move_field、jump_to_field 和 submit_answers 在离开字段或提交前调用它。

调用图:调用 3 个内部函数(capture_composer_draft, current_answer_mut, current_field_is_select);被 3 处调用(jump_to_field, move_field, submit_answers)。

McpServerElicitationOverlay::clear_current_draft871–882 ↗
fn clear_current_draft(&mut self)

作用:清空当前文本字段的草稿。它用于 Ctrl+C 第一次按下时只清输入,而不是立刻取消整个请求。

数据流:进去的是可变 Overlay;如果是文本题,它把当前答案重置为空、标为未提交,并清空输入框;出来是空输入状态。

调用关系:on_ctrl_c 会调用它;选择题不会被它影响。

调用图:调用 4 个内部函数(move_cursor_to_end, set_text_content, current_answer_mut, current_field_is_select);被 1 处调用(on_ctrl_c);外部调用 3 个(new, new, default)。

McpServerElicitationOverlay::answer_placeholder884–892 ↗
fn answer_placeholder(&self) -> &'static str

作用:决定输入框里灰色占位提示写什么。必填字段显示普通提示,选填字段会说明可选。

数据流:进去的是 Overlay;它读取当前字段是否 required;出来是固定字符串“Type your answer”或“Type your answer (optional)”。

调用关系:restore_current_draft 在每次切换字段时调用它,确保输入框提示符合当前字段。

调用图:调用 1 个内部函数(current_field);被 1 处调用(restore_current_draft)。

McpServerElicitationOverlay::current_field_is_select894–899 ↗
fn current_field_is_select(&self) -> bool

作用:判断当前字段是不是选择题。很多行为会因此不同,比如不显示光标、用上下键选项、不能粘贴。

数据流:进去的是 Overlay;它查看当前字段 input 类型;出来是布尔值。

调用关系:渲染、按键处理、粘贴、清草稿、光标位置和高度计算都会用它分支。

调用图:被 12 处调用(clear_current_draft, cursor_pos, footer_tips, handle_key_event, handle_paste, input_height, on_ctrl_c, render, render_footer, render_input (+2 more));外部调用 1 个(matches!)。

McpServerElicitationOverlay::current_field_is_secret901–906 ↗
fn current_field_is_secret(&self) -> bool

作用:判断当前文本字段是否是秘密输入,比如密码。秘密字段会用星号遮住内容。

数据流:进去的是 Overlay;它查看当前字段是否是 Text 且 secret 为 true;出来是布尔值。

调用关系:render_input 会根据它选择普通渲染还是带掩码渲染。

调用图:被 1 处调用(render_input);外部调用 1 个(matches!)。

McpServerElicitationOverlay::selected_option_index908–911 ↗
fn selected_option_index(&self) -> Option<usize>

作用:返回当前选择题选中的选项编号。它让界面知道箭头该画在哪一行。

数据流:进去的是 Overlay;它读取当前答案里的 selection.selected_idx;出来是可选下标。

调用关系:option_rows、render_footer 和 handle_key_event 都会用它显示或判断当前选择。

调用图:调用 1 个内部函数(current_answer);被 3 处调用(handle_key_event, option_rows, render_footer)。

McpServerElicitationOverlay::options_len913–915 ↗
fn options_len(&self) -> usize

作用:返回当前选择题有多少个选项。它用于防止选择越界。

数据流:进去的是 Overlay;它读取 current_options 的长度;出来是选项数量。

调用关系:handle_key_event、select_current_option、option_index_for_digit 和 render_footer 用它校验范围或显示总数。

调用图:调用 1 个内部函数(current_options);被 4 处调用(handle_key_event, option_index_for_digit, render_footer, select_current_option)。

McpServerElicitationOverlay::current_options917–922 ↗
fn current_options(&self) -> &[McpServerElicitationOption]

作用:取出当前字段的选项列表。如果当前不是选择题,就返回空列表。

数据流:进去的是 Overlay;它查看当前字段输入类型;出来是选项切片或空切片。

调用关系:option_rows 和 options_len 会调用它,是选择题显示和操作的基础。

调用图:调用 1 个内部函数(current_field);被 2 处调用(option_rows, options_len)。

McpServerElicitationOverlay::option_rows924–946 ↗
fn option_rows(&self) -> Vec<GenericDisplayRow>

作用:把选项变成可以渲染的菜单行。它会加上序号和当前选中箭头。

数据流:进去的是 Overlay;它读取当前选项和选中下标,给每项生成名称、说明和换行缩进;出来是一组 GenericDisplayRow。

调用关系:options_required_height 用它测量高度,render_input 用它实际画选项列表。

调用图:调用 2 个内部函数(current_options, selected_option_index);被 2 处调用(options_required_height, render_input)。

McpServerElicitationOverlay::wrapped_prompt_lines948–953 ↗
fn wrapped_prompt_lines(&self, width: u16) -> Vec<String>

作用:把顶部提示文字按终端宽度自动换行。这样窄窗口里文字不会溢出屏幕。

数据流:进去的是宽度;它先生成当前提示文本,再用 textwrap 按宽度切成多行;出来是字符串行列表。

调用关系:desired_height、render、render_prompt 和 cursor_pos 都用它计算布局。

调用图:调用 1 个内部函数(current_prompt_text);被 4 处调用(cursor_pos, desired_height, render, render_prompt);外部调用 1 个(wrap)。

McpServerElicitationOverlay::current_prompt_text955–983 ↗
fn current_prompt_text(&self) -> String

作用:生成当前字段要展示的完整提示文字。它会把请求说明、工具参数摘要、字段标题和字段说明组合起来。

数据流:进去的是 Overlay;它先格式化审批消息,再读取当前字段 label 和 prompt,去掉重复或空内容;出来是一段带段落分隔的文本。

调用关系:wrapped_prompt_lines 调用它;工具审批参数显示由 format_tool_approval_display_message 负责。

调用图:调用 2 个内部函数(current_field, format_tool_approval_display_message);被 1 处调用(wrapped_prompt_lines);外部调用 2 个(new, format!)。

McpServerElicitationOverlay::options_required_height1023–1036 ↗
fn options_required_height(&self, width: u16) -> u16

作用:计算当前选择题完整显示所有选项大概需要多高。它帮助界面决定输入区域大小以及是否显示“第几个选项”的提示。

数据流:进去的是宽度;它生成选项行,准备滚动选择状态,并测量菜单行高度;出来是需要的高度。

调用关系:input_height 用它给选择题算高度,render_footer 用它判断有没有选项被遮住。

调用图:调用 3 个内部函数(current_answer, option_rows, measure_rows_height);被 2 处调用(input_height, render_footer)。

McpServerElicitationOverlay::input_height1038–1045 ↗
fn input_height(&self, width: u16) -> u16

作用:计算输入区域该占多高。选择题按选项高度算,文本题按输入框期望高度算并限制范围。

数据流:进去的是宽度;它判断当前字段类型,选择题返回选项高度,文本题询问 ChatComposer 后夹在最小高度和最大高度之间;出来是高度值。

调用关系:desired_height 调用它来估算整个弹窗高度。

调用图:调用 3 个内部函数(desired_height, current_field_is_select, options_required_height);被 1 处调用(desired_height)。

McpServerElicitationOverlay::move_field1047–1057 ↗
fn move_field(&mut self, next: bool)

作用:切换到上一个或下一个字段。它让多字段表单像翻页一样回答。

数据流:进去的是方向 next;它先保存当前草稿,再按字段数量循环移动下标,清掉错误并恢复新字段草稿;出来是当前字段改变后的 Overlay。

调用关系:handle_key_event 的快捷键和 go_next_or_submit 都会调用它。

调用图:调用 3 个内部函数(field_count, restore_current_draft, save_current_draft);被 2 处调用(go_next_or_submit, handle_key_event)。

McpServerElicitationOverlay::jump_to_field1059–1066 ↗
fn jump_to_field(&mut self, idx: usize)

作用:直接跳到指定字段。它主要用于提交时发现必填没答,立刻把用户带到那个字段。

数据流:进去的是目标下标;如果下标合法,它保存当前草稿、更新 current_idx、恢复目标字段草稿;否则不做事。

调用关系:submit_answers 在校验失败时调用它。

调用图:调用 3 个内部函数(field_count, restore_current_draft, save_current_draft);被 1 处调用(submit_answers)。

McpServerElicitationOverlay::field_value1068–1088 ↗
fn field_value(&self, idx: usize) -> Option<Value>

作用:把某个字段当前答案转成 JSON 值。提交表单时需要这种标准格式。

数据流:进去的是字段下标;它读取字段定义和答案状态,选择题取已提交选项的值,文本题取已提交且非空的文字;出来是可选 JSON 值。

调用关系:submit_answers 用它组装最终内容,is_current_field_answered 和必填校验也靠它判断是否已答。

调用图:被 2 处调用(is_current_field_answered, submit_answers)。

McpServerElicitationOverlay::required_unanswered_count1090–1097 ↗
fn required_unanswered_count(&self) -> usize

作用:统计还有多少必填字段没答。它用于顶部进度提示,让用户知道还缺几项。

数据流:进去的是 Overlay;它遍历所有字段,检查 required 且 field_value 为空的项;出来是数量。

调用关系:render 在画进度行时调用它。

调用图:被 1 处调用(render)。

McpServerElicitationOverlay::first_required_unanswered_index1099–1106 ↗
fn first_required_unanswered_index(&self) -> Option<usize>

作用:找到第一个没答的必填字段。提交失败时用它把用户带到最先需要补的地方。

数据流:进去的是 Overlay;它按顺序检查字段是否必填且没有 field_value;出来是第一个下标或 None。

调用关系:submit_answers 在真正发送前调用它做校验。

调用图:被 1 处调用(submit_answers)。

McpServerElicitationOverlay::is_current_field_answered1108–1110 ↗
fn is_current_field_answered(&self) -> bool

作用:判断当前字段是否已经有有效答案。界面用它决定提示文字是否需要高亮。

数据流:进去的是 Overlay;它用 current_index 找到当前字段并调用 field_value;出来是布尔值。

调用关系:render_prompt 调用它,未回答时把提示显示得更醒目。

调用图:调用 2 个内部函数(current_index, field_value);被 1 处调用(render_prompt)。

McpServerElicitationOverlay::option_index_for_digit1112–1119 ↗
fn option_index_for_digit(&self, ch: char) -> Option<usize>

作用:把用户按下的数字键转换成选项下标。比如按 1 就选第一个选项。

数据流:进去的是字符;它尝试转成 1 到 9 的数字,并检查是否在选项数量内;出来是可选下标。

调用关系:handle_key_event 在选择题里处理数字快捷键时调用它。

调用图:调用 1 个内部函数(options_len);被 1 处调用(handle_key_event)。

McpServerElicitationOverlay::select_current_option1121–1127 ↗
fn select_current_option(&mut self, committed: bool)

作用:确认当前选择题选中的选项。它还会把选择限制在合法范围内。

数据流:进去的是 committed 标记;它读取选项数量,修正当前选择下标,并设置答案是否已提交;出来是更新后的答案状态。

调用关系:handle_key_event 在空格、回车或数字选择时调用它。

调用图:调用 2 个内部函数(current_answer_mut, options_len);被 1 处调用(handle_key_event)。

McpServerElicitationOverlay::clear_selection1129–1134 ↗
fn clear_selection(&mut self)

作用:清除当前选择题的选择。它让用户可以撤销刚才的选项。

数据流:进去的是可变 Overlay;它重置当前答案的滚动选择状态,并标成未提交;出来是没有选中项的状态。

调用关系:handle_key_event 在 Backspace 或 Delete 时调用它。

调用图:调用 1 个内部函数(current_answer_mut);被 1 处调用(handle_key_event)。

McpServerElicitationOverlay::dispatch_cancel1136–1145 ↗
fn dispatch_cancel(&self)

作用:向主程序发送“取消这次提问”的结果。它是 Esc 和 Ctrl+C 最终取消时的发送通道。

数据流:进去的是 Overlay;它读取线程、服务器名和请求编号,发送 Cancel 决定,content 和 meta 都为空;出来是一个应用事件被发出。

调用关系:handle_key_event 和 on_ctrl_c 会调用它;真正发送通过 AppEventSender::resolve_elicitation 完成。

调用图:调用 1 个内部函数(resolve_elicitation);被 2 处调用(handle_key_event, on_ctrl_c)。

McpServerElicitationOverlay::advance_queue_or_complete1147–1155 ↗
fn advance_queue_or_complete(&mut self)

作用:当前请求结束后,切到队列里的下一个请求;如果没有下一个,就标记弹窗完成。它让多个请求按先来先服务处理。

数据流:进去的是可变 Overlay;它从队列头取请求,有则替换当前请求并重置界面,没有则把 done 设为 true;出来是进入下个请求或结束状态。

调用关系:submit_answers 成功发送后和 dismiss_resolved_request 移除当前请求后都会调用它。

调用图:调用 2 个内部函数(reset_for_request, restore_current_draft);被 2 处调用(dismiss_resolved_request, submit_answers);外部调用 1 个(pop_front)。

McpServerElicitationOverlay::submit_answers1157–1212 ↗
fn submit_answers(&mut self)

作用:提交当前表单或审批决定。它负责最后校验、整理答案,并把结果发回主程序。

数据流:进去的是可变 Overlay;它先保存草稿,检查必填项,再根据模式发送 Accept、Decline 或 Cancel,普通表单会组装 JSON content;最后推进队列或完成。

调用关系:go_next_or_submit 在最后一个字段按回车时调用它;发送结果交给 AppEventSender::resolve_elicitation。

调用图:调用 6 个内部函数(resolve_elicitation, advance_queue_or_complete, field_value, first_required_unanswered_index, jump_to_field, save_current_draft);被 1 处调用(go_next_or_submit);外部调用 2 个(Object, json!)。

McpServerElicitationOverlay::dismiss_resolved_request1214–1233 ↗
fn dismiss_resolved_request(&mut self, request: &ResolvedAppServerRequest) -> bool

作用:当外部告诉界面某个请求已经解决时,把它从当前弹窗或队列里移除。这样不会让用户回答一个已经过期的问题。

数据流:进去的是已解决请求信息;它只处理 MCP elicitation 类型,先从队列删匹配项,如果当前请求也匹配就切到下一个;出来是是否真的移除了请求。

调用关系:dismiss_app_server_request 调用它;如果移除的是当前请求,会继续调用 advance_queue_or_complete。

调用图:调用 1 个内部函数(advance_queue_or_complete);被 1 处调用(dismiss_app_server_request);外部调用 2 个(len, retain)。

McpServerElicitationOverlay::go_next_or_submit1235–1241 ↗
fn go_next_or_submit(&mut self)

作用:按回车后的统一动作:不是最后一个字段就去下一个,是最后一个就提交。它让用户可以连续按回车完成表单。

数据流:进去的是可变 Overlay;它比较当前下标和字段数量;出来是字段前进或提交结果。

调用关系:handle_key_event 和 handle_composer_input_result 在用户确认答案后调用它。

调用图:调用 4 个内部函数(current_index, field_count, move_field, submit_answers);被 2 处调用(handle_composer_input_result, handle_key_event)。

McpServerElicitationOverlay::apply_submission_to_draft1243–1263 ↗
fn apply_submission_to_draft(&mut self, text: String, text_elements: Vec<TextElement>)

作用:把输入框提交出来的文字写回当前字段草稿。它把“按下回车的内容”变成已确认答案。

数据流:进去的是文本和文字元素;它保留本地图片路径,更新当前答案草稿、清空 pending_pastes,并按文本是否为空设置已提交;出来是同步后的答案和输入框。

调用关系:handle_composer_input_result 在 ChatComposer 报告 Submitted 或 Queued 时调用它。

调用图:调用 5 个内部函数(local_images, move_cursor_to_end, set_footer_hint_override, set_text_content, current_answer_mut);被 1 处调用(handle_composer_input_result);外部调用 1 个(new)。

McpServerElicitationOverlay::handle_composer_input_result1265–1283 ↗
fn handle_composer_input_result(&mut self, result: InputResult) -> bool

作用:处理文本输入框返回的结果,尤其是提交。它把输入框世界和表单流程连接起来。

数据流:进去的是 InputResult;如果是提交或排队提交,它保存草稿、清掉错误、前进或提交,并返回 true;其他结果返回 false。

调用关系:handle_key_event 把按键交给 ChatComposer 后调用它,后续动作交给 apply_submission_to_draft 和 go_next_or_submit。

调用图:调用 2 个内部函数(apply_submission_to_draft, go_next_or_submit);被 1 处调用(handle_key_event)。

McpServerElicitationOverlay::render_prompt1285–1310 ↗
fn render_prompt(&self, area: Rect, buf: &mut Buffer)

作用:画出表单上方的问题说明。未回答时会用更醒目的颜色提醒用户。

数据流:进去的是绘制区域和缓冲区;它按宽度取得换行后的提示行,并逐行写入缓冲区;出来是屏幕缓冲区被更新。

调用关系:Renderable::render 调用它画提示部分;提示文本来自 wrapped_prompt_lines。

调用图:调用 2 个内部函数(is_current_field_answered, wrapped_prompt_lines);被 1 处调用(render);外部调用 2 个(from, new)。

McpServerElicitationOverlay::render_input1312–1334 ↗
fn render_input(&self, area: Rect, buf: &mut Buffer)

作用:画出中间的输入区域。选择题画选项列表,文本题画输入框,秘密文本会用星号遮挡。

数据流:进去的是绘制区域和缓冲区;它根据当前字段类型渲染选项行或 ChatComposer;出来是输入区域被写入缓冲区。

调用关系:Renderable::render 调用它;选择题依赖 option_rows 和 render_rows,文本题依赖 ChatComposer 的 render 或 render_with_mask。

调用图:调用 7 个内部函数(render, render_with_mask, current_answer, current_field_is_secret, current_field_is_select, option_rows, render_rows);被 1 处调用(render)。

McpServerElicitationOverlay::desired_height1389–1399 ↗
fn desired_height(&self, width: u16) -> u16

作用:告诉外层布局这个弹窗理想上需要多高。这样底部面板能给它分配合适空间。

数据流:进去的是可用宽度;它计算边框内宽度、提示行数、输入高度、页脚行数和菜单内边距;出来是至少 MIN_OVERLAY_HEIGHT 的高度。

调用关系:这是 Renderable 接口的一部分,外层布局会调用它;内部会用 wrapped_prompt_lines、input_height 和 footer_tip_lines。

调用图:调用 5 个内部函数(footer_tip_lines, input_height, wrapped_prompt_lines, menu_surface_inset, menu_surface_padding_height);外部调用 1 个(new)。

McpServerElicitationOverlay::render1401–1473 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:把整个提问弹窗画到终端缓冲区。它负责把进度、提示、输入和页脚四块排好。

数据流:进去的是总绘制区域和缓冲区;它先画菜单外壳,再分配进度区、提示区、输入区和页脚区,最后逐块渲染;出来是完整弹窗画面。

调用关系:这是 Renderable 接口的核心函数;测试里的 render_snapshot 会调用它检查界面效果。

调用图:调用 10 个内部函数(current_field_is_select, current_index, field_count, footer_tip_lines, render_footer, render_input, render_prompt, required_unanswered_count, wrapped_prompt_lines, render_menu_surface);被 1 处调用(render_snapshot);外部调用 4 个(from, new, format!, from)。

McpServerElicitationOverlay::cursor_pos1475–1505 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:告诉终端光标应该放在哪里。选择题没有文本光标,文本题则把光标交给输入框计算。

数据流:进去的是弹窗区域;它重算和 render 相同的布局,定位输入框区域;出来是光标坐标或 None。

调用关系:这是 Renderable 接口的一部分;它依赖 menu_surface_inset、wrapped_prompt_lines、footer_tip_lines 和 ChatComposer::cursor_pos。

调用图:调用 5 个内部函数(cursor_pos, current_field_is_select, footer_tip_lines, wrapped_prompt_lines, menu_surface_inset);外部调用 1 个(from)。

McpServerElicitationOverlay::prefer_esc_to_handle_key_event1509–1511 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

作用:声明这个弹窗希望自己优先处理 Esc 键。因为 Esc 在这里意味着取消请求,是重要动作。

数据流:进去的是 Overlay;它不读取复杂状态,直接返回 true;没有其他副作用。

调用关系:这是 BottomPaneView 接口给外层按键分发器看的标志。

McpServerElicitationOverlay::handle_key_event1513–1643 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

作用:处理用户按键。它是选择、输入、切换字段、提交和取消这些交互的总入口。

数据流:进去的是一个键盘事件;它忽略按键释放,处理 Esc、字段切换、选择题上下移动和确认,文本题则转交 ChatComposer;出来是 Overlay 状态变化,必要时发送取消或提交事件。

调用关系:外层 TUI 在用户按键时调用它;它会调 dispatch_cancel、move_field、select_current_option、go_next_or_submit、handle_composer_input_result 等内部部件。

调用图:调用 13 个内部函数(handle_key_event, capture_composer_draft, clear_selection, current_answer_mut, current_field_is_select, dispatch_cancel, go_next_or_submit, handle_composer_input_result, move_field, option_index_for_digit (+3 more));外部调用 1 个(matches!)。

McpServerElicitationOverlay::terminal_title_requires_action1645–1647 ↗
fn terminal_title_requires_action(&self) -> bool

作用:告诉外层:这个弹窗需要用户采取行动。终端标题或状态栏可以据此提示用户别忽略。

数据流:进去的是 Overlay;它直接返回 true;不改动任何状态。

调用关系:这是 BottomPaneView 接口的一部分,由外层界面查询。

McpServerElicitationOverlay::on_ctrl_c1649–1658 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

作用:处理 Ctrl+C。文本框里有内容时先清空内容;没有内容或是选择题时才取消整个请求。

数据流:进去的是可变 Overlay;它检查当前字段和输入框内容,可能清草稿,也可能发送 Cancel 并标记 done;出来是 CancellationEvent::Handled。

调用关系:外层收到 Ctrl+C 时调用它;清空动作交给 clear_current_draft,取消动作交给 dispatch_cancel。

调用图:调用 4 个内部函数(current_text_with_pending, clear_current_draft, current_field_is_select, dispatch_cancel)。

McpServerElicitationOverlay::is_complete1660–1662 ↗
fn is_complete(&self) -> bool

作用:告诉外层这个弹窗是否已经结束。结束后外层可以把它从底部面板移走。

数据流:进去的是 Overlay;它读取 done 字段;出来是布尔值。

调用关系:这是 BottomPaneView 接口的一部分,submit、取消或队列处理完后会变成 true。

McpServerElicitationOverlay::handle_paste1664–1673 ↗
fn handle_paste(&mut self, pasted: String) -> bool

作用:处理用户粘贴文字。选择题不接受粘贴,文本题会把粘贴交给输入框。

数据流:进去的是粘贴字符串;如果为空或当前是选择题就返回 false,否则清掉错误、标记答案未提交,并让 ChatComposer 处理粘贴;出来是是否接受了粘贴。

调用关系:外层粘贴事件调用它;实际粘贴细节由 ChatComposer::handle_paste 处理。

调用图:调用 3 个内部函数(handle_paste, current_answer_mut, current_field_is_select)。

McpServerElicitationOverlay::flush_paste_burst_if_due1675–1677 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

作用:在合适时机把一连串快速粘贴内容冲刷进输入框。它避免大段粘贴被拆得太乱。

数据流:进去的是可变 Overlay;它转交给 ChatComposer 检查粘贴 burst 是否到期;出来是是否刷新了内容。

调用关系:这是 BottomPaneView 接口的一部分,外层定期或事件循环中会调用它。

调用图:调用 1 个内部函数(flush_paste_burst_if_due)。

McpServerElicitationOverlay::is_in_paste_burst1679–1681 ↗
fn is_in_paste_burst(&self) -> bool

作用:询问当前是否正在处理一段连续粘贴。外层可以据此调整刷新或输入处理。

数据流:进去的是 Overlay;它读取 ChatComposer 的粘贴状态;出来是布尔值。

调用关系:这是 BottomPaneView 接口的一部分,具体状态由 ChatComposer 管理。

调用图:调用 1 个内部函数(is_in_paste_burst)。

McpServerElicitationOverlay::try_consume_mcp_server_elicitation_request1683–1689 ↗
fn try_consume_mcp_server_elicitation_request(
        &mut self,
        request: McpServerElicitationFormRequest,
    ) -> Option<McpServerElicitationFormRequest>

作用:当已有弹窗时,接收新的 MCP 提问并排到队尾。它避免新请求打断当前用户正在回答的问题。

数据流:进去的是新表单请求;它把请求 push 到队列尾部;出来固定是 None,表示请求已被当前弹窗吸收。

调用关系:外层如果发现当前底部面板已经是此类 Overlay,会调用它合并后续请求;完成当前请求后由 advance_queue_or_complete 取出。

调用图:外部调用 1 个(push_back)。

McpServerElicitationOverlay::dismiss_app_server_request1691–1693 ↗
fn dismiss_app_server_request(&mut self, request: &ResolvedAppServerRequest) -> bool

作用:响应外部“这个服务器请求已解决”的通知。它把具体处理交给内部的 dismiss_resolved_request。

数据流:进去的是已解决请求;它调用 dismiss_resolved_request;出来是是否移除了对应请求。

调用关系:这是 BottomPaneView 接口的一部分,外层同步服务器状态时会调用它。

调用图:调用 1 个内部函数(dismiss_resolved_request)。

tests::test_sender1746–1749 ↗
fn test_sender() -> (AppEventSender, UnboundedReceiver<AppEvent>)

作用:创建测试用的事件发送器和接收器。这样测试可以检查弹窗有没有发出正确事件。

数据流:进去没有参数;它创建一个无界通道,并把发送端包装成 AppEventSender;出来是发送器和接收器。

调用关系:提交、取消和队列相关测试都会用它接收事件。

调用图:调用 1 个内部函数(new)。

tests::form_request1751–1767 ↗
fn form_request(
        message: &str,
        requested_schema: Value,
        meta: Option<Value>,
    ) -> McpServerElicitationRequestParams

作用:快速构造测试用的 MCP 表单请求参数。它让测试不用每次手写完整协议结构。

数据流:进去的是消息、schema JSON 和元数据;它填入固定线程、turn、服务器名,并把 schema 反序列化成协议类型;出来是 McpServerElicitationRequestParams。

调用关系:大多数解析和界面测试都用它准备输入,再交给 from_form_request。

调用图:外部调用 1 个(from_value)。

tests::request_id1769–1771 ↗
fn request_id(value: &str) -> AppServerRequestId

作用:把普通字符串包装成测试用请求编号。这样断言里写请求 id 更方便。

数据流:进去的是字符串;它转成 AppServerRequestId::String;出来是请求编号。

调用关系:from_form_request 和多个事件断言会用它保持请求编号一致。

调用图:外部调用 1 个(String)。

tests::from_form_request1773–1782 ↗
fn from_form_request(
        thread_id: ThreadId,
        request: McpServerElicitationRequestParams,
    ) -> Option<McpServerElicitationFormRequest>

作用:测试辅助函数,把测试请求转成内部表单请求。它少写一层重复调用。

数据流:进去的是线程编号和请求参数;它固定使用 request-1 调用 from_app_server_request;出来是可选 McpServerElicitationFormRequest。

调用关系:解析测试、提交测试和快照测试大量使用它。

调用图:调用 1 个内部函数(from_app_server_request);外部调用 1 个(request_id)。

tests::empty_object_schema1784–1789 ↗
fn empty_object_schema() -> Value

作用:生成一个没有字段的 object schema。测试用它模拟“只有消息,需要用户批准”的请求。

数据流:进去没有参数;它用 JSON 宏创建 type 为 object、properties 为空的值;出来是 JSON schema。

调用关系:审批 fallback、工具审批和工具建议相关测试都会用它。

调用图:外部调用 1 个(json!)。

tests::tool_approval_meta1791–1832 ↗
fn tool_approval_meta(
        persist_modes: &[&str],
        tool_params: Option<Value>,
        tool_params_display: Option<Vec<(&str, Value, &str)>>,
    ) -> Option<Value>

作用:生成测试用的工具审批元数据。它可以带记住模式、原始参数和显示参数。

数据流:进去的是持久化模式列表、可选工具参数和可选显示参数;它组装成 JSON 对象;出来是 Some(Value::Object)。

调用关系:工具审批选项、参数摘要和持久允许测试都靠它构造场景。

调用图:外部调用 4 个(Array, Object, String, from_iter)。

tests::snapshot_buffer1834–1844 ↗
fn snapshot_buffer(buf: &Buffer) -> String

作用:把终端绘制缓冲区转成普通字符串。快照测试用它比较画面是否变化。

数据流:进去的是 Buffer;它逐行逐列读取每个格子的字符并拼成文本;出来是多行字符串。

调用关系:render_snapshot 调用它,把 Overlay 的渲染结果变成可断言内容。

调用图:外部调用 3 个(area, new, new)。

tests::render_snapshot1846–1850 ↗
fn render_snapshot(overlay: &McpServerElicitationOverlay, area: Rect) -> String

作用:渲染弹窗并得到字符串快照。它是界面快照测试的公共入口。

数据流:进去的是 Overlay 和区域;它创建空 Buffer,调用 overlay.render,然后转成字符串;出来是画面文本。

调用关系:多个 snapshot 测试调用它验证不同请求的显示效果。

调用图:调用 1 个内部函数(render);外部调用 2 个(empty, snapshot_buffer)。

tests::parses_boolean_form_request1853–1908 ↗
fn parses_boolean_form_request()

作用:测试布尔字段 schema 能正确变成真假选项表单。它防止基础表单解析退化。

数据流:进去是测试运行环境;它构造带 required 布尔字段的请求,解析后和预期结构比较;出来是测试通过或失败。

调用关系:它覆盖 from_form_request、from_app_server_request、from_parts、parse_fields_from_schema 和 parse_field 的布尔分支。

调用图:调用 1 个内部函数(default);外部调用 4 个(assert_eq!, json!, form_request, from_form_request)。

tests::unsupported_numeric_form_falls_back1911–1930 ↗
fn unsupported_numeric_form_falls_back()

作用:测试数字字段目前不支持时会返回 None。它确保界面不会错误显示不能处理的输入类型。

数据流:进去是测试运行环境;它构造 integer 字段请求并解析;出来断言结果为 None。

调用关系:它覆盖 parse_field 对 Number 类型的拒绝路径。

调用图:调用 1 个内部函数(default);外部调用 4 个(assert_eq!, json!, form_request, from_form_request)。

tests::empty_object_schema_uses_approval_actions1933–1983 ↗
fn empty_object_schema_uses_approval_actions()

作用:测试空对象 schema 会退化成普通审批选项。这样只有一句消息的请求仍然能被用户允许、拒绝或取消。

数据流:进去是测试运行环境;它构造空 schema 请求,解析后检查生成的 Allow、Deny、Cancel 选项;出来是断言结果。

调用关系:它验证 from_parts 的 message-only approval fallback。

调用图:调用 1 个内部函数(default);外部调用 4 个(assert_eq!, empty_object_schema, form_request, from_form_request)。

tests::empty_tool_approval_schema_uses_approval_actions1986–2035 ↗
fn empty_tool_approval_schema_uses_approval_actions()

作用:测试工具调用审批在空 schema 下会显示适合工具的选项文案。它防止把工具取消误写成普通拒绝流程。

数据流:进去是测试运行环境;它构造带工具审批元数据的空 schema 请求,解析并比较预期;出来是测试结果。

调用关系:它覆盖 from_parts 识别 APPROVAL_KIND_MCP_TOOL_CALL 的路径。

调用图:调用 1 个内部函数(default);外部调用 5 个(assert_eq!, empty_object_schema, form_request, from_form_request, tool_approval_meta)。

tests::tool_suggestion_meta_is_parsed_into_request_payload2038–2068 ↗
fn tool_suggestion_meta_is_parsed_into_request_payload()

作用:测试 connector 工具建议元数据能被解析出来。它保证工具推荐不会丢掉原因、名称和安装地址。

数据流:进去是测试运行环境;它构造 tool_suggestion 元数据并解析请求;出来断言 tool_suggestion 内容正确。

调用关系:它直接验证 parse_tool_suggestion_request 和 tool_suggestion 访问器。

调用图:调用 1 个内部函数(default);外部调用 5 个(assert_eq!, json!, empty_object_schema, form_request, from_form_request)。

tests::plugin_tool_suggestion_meta_without_install_url_is_parsed_into_request_payload2071–2100 ↗
fn plugin_tool_suggestion_meta_without_install_url_is_parsed_into_request_payload()

作用:测试 plugin 工具建议即使没有安装地址也能解析。它保证可选字段不会让整个请求失败。

数据流:进去是测试运行环境;它构造 plugin install 建议但不带 install_url;出来断言解析出的 install_url 是 None。

调用关系:它覆盖 parse_tool_suggestion_request 的 plugin 分支和可选 URL 分支。

调用图:调用 1 个内部函数(default);外部调用 5 个(assert_eq!, json!, empty_object_schema, form_request, from_form_request)。

tests::tool_approval_display_params_prefer_explicit_display_order2103–2147 ↗
fn tool_approval_display_params_prefer_explicit_display_order()

作用:测试工具审批参数优先使用显式显示列表和顺序。它确保用户看到的是后台精心挑选的摘要,而不是乱序原始参数。

数据流:进去是测试运行环境;它同时提供原始参数和显示参数列表,解析后检查只采用显示列表顺序;出来是断言结果。

调用关系:它验证 parse_tool_approval_display_params 的优先级逻辑。

调用图:调用 1 个内部函数(default);外部调用 7 个(assert_eq!, json!, empty_object_schema, form_request, from_form_request, tool_approval_meta, vec!)。

tests::submit_sends_accept_with_typed_content2150–2201 ↗
fn submit_sends_accept_with_typed_content()

作用:测试提交普通表单会发送 Accept 和填写内容。它保证用户选择的值真的回到主程序。

数据流:进去是测试运行环境;它创建布尔表单 Overlay,选择当前选项并提交,然后从通道取事件比较;出来是断言事件正确。

调用关系:它覆盖 select_current_option、submit_answers 和 AppEventSender 发送 ResolveElicitation 的路径。

调用图:调用 2 个内部函数(default, new);外部调用 6 个(assert_eq!, panic!, json!, form_request, from_form_request, test_sender)。

tests::horizontal_list_keys_move_between_select_fields2204–2238 ↗
fn horizontal_list_keys_move_between_select_fields()

作用:测试选择题字段之间可以用横向按键切换。它保证列表按键映射在表单里可用。

数据流:进去是测试运行环境;它创建两个布尔字段,发送按键事件,检查 current_idx 改变;出来是断言结果。

调用关系:它覆盖 handle_key_event 中选择题左右移动字段的逻辑。

调用图:调用 2 个内部函数(default, new);外部调用 7 个(Char, new, assert_eq!, json!, form_request, from_form_request, test_sender)。

tests::empty_tool_approval_schema_session_choice_sets_persist_meta2241–2292 ↗
fn empty_tool_approval_schema_session_choice_sets_persist_meta()

作用:测试选择“本会话允许”会带上正确 meta。它保证后台知道这次允许要记到会话级别。

数据流:进去是测试运行环境;它构造支持 session 和 always 的工具审批,选中 session 选项并提交;出来断言事件 meta 里有 session persist。

调用关系:它覆盖 approval_supports_persist_mode、submit_answers 的 APPROVAL_ACCEPT_SESSION_VALUE 分支。

调用图:调用 2 个内部函数(default, new);外部调用 7 个(assert_eq!, panic!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta)。

tests::empty_tool_approval_schema_always_allow_sets_persist_meta2295–2346 ↗
fn empty_tool_approval_schema_always_allow_sets_persist_meta()

作用:测试选择“总是允许”会带上正确 meta。它保证长期记住选择的意图能传回后台。

数据流:进去是测试运行环境;它构造支持持久化的工具审批,选中 always 选项并提交;出来断言事件 meta 里有 always persist。

调用关系:它覆盖 submit_answers 的 APPROVAL_ACCEPT_ALWAYS_VALUE 分支。

调用图:调用 2 个内部函数(default, new);外部调用 7 个(assert_eq!, panic!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta)。

tests::ctrl_c_cancels_elicitation2349–2397 ↗
fn ctrl_c_cancels_elicitation()

作用:测试 Ctrl+C 会取消当前提问并发送取消事件。它保证紧急退出路径可用。

数据流:进去是测试运行环境;它创建表单 Overlay,调用 on_ctrl_c,再读取事件;出来断言发送的是 Cancel。

调用关系:它覆盖 on_ctrl_c 和 dispatch_cancel。

调用图:调用 2 个内部函数(default, new);外部调用 6 个(assert_eq!, panic!, json!, form_request, from_form_request, test_sender)。

tests::queues_requests_fifo2400–2469 ↗
fn queues_requests_fifo()

作用:测试多个请求按先进先出顺序处理。它防止后来请求插队或覆盖当前请求。

数据流:进去是测试运行环境;它创建三个请求,把后两个排队,提交第一个和第二个后检查当前消息;出来是顺序断言。

调用关系:它覆盖 try_consume_mcp_server_elicitation_request、submit_answers 和 advance_queue_or_complete。

调用图:调用 2 个内部函数(default, new);外部调用 5 个(assert_eq!, json!, form_request, from_form_request, test_sender)。

tests::resolved_request_dismisses_overlay_without_emitting_events2472–2537 ↗
fn resolved_request_dismisses_overlay_without_emitting_events()

作用:测试外部已解决请求会从弹窗/队列消失,但不会额外发送事件。它防止重复回复服务器。

数据流:进去是测试运行环境;它创建当前请求和排队请求,分别 dismiss,并检查当前请求推进、完成状态和事件通道为空;出来是断言结果。

调用关系:它覆盖 dismiss_app_server_request、dismiss_resolved_request 和 advance_queue_or_complete。

调用图:调用 3 个内部函数(default, from_app_server_request, new);外部调用 8 个(assert!, assert_eq!, from_value, json!, form_request, from_form_request, request_id, test_sender)。

tests::boolean_form_snapshot2540–2570 ↗
fn boolean_form_snapshot()

作用:测试布尔表单的终端画面快照。它帮助发现 UI 布局或文字意外变化。

数据流:进去是测试运行环境;它构造布尔表单 Overlay 并渲染到固定区域;出来是和保存快照比较。

调用关系:它通过 render_snapshot 覆盖 Renderable::render 的普通表单显示。

调用图:调用 2 个内部函数(default, new);外部调用 5 个(assert_snapshot!, json!, form_request, from_form_request, test_sender)。

tests::approval_form_tool_approval_snapshot2573–2597 ↗
fn approval_form_tool_approval_snapshot()

作用:测试工具审批表单的基础画面快照。它确保工具运行确认界面长得符合预期。

数据流:进去是测试运行环境;它构造空 schema 的工具审批 Overlay 并渲染;出来是快照断言。

调用关系:它覆盖 from_parts 的工具审批 fallback 和 render。

调用图:调用 2 个内部函数(default, new);外部调用 6 个(assert_snapshot!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta)。

tests::message_only_form_snapshot2600–2620 ↗
fn message_only_form_snapshot()

作用:测试只有消息、没有字段的普通审批画面。它保证简单确认请求也有合适 UI。

数据流:进去是测试运行环境;它构造空对象 schema 的普通请求并渲染;出来是快照比较。

调用关系:它覆盖 message-only approval fallback 的渲染路径。

调用图:调用 2 个内部函数(default, new);外部调用 5 个(assert_snapshot!, empty_object_schema, form_request, from_form_request, test_sender)。

tests::message_only_form_with_persist_options_snapshot2623–2648 ↗
fn message_only_form_with_persist_options_snapshot()

作用:测试普通消息审批带“记住选择”选项时的画面。它保证持久化选项会显示出来。

数据流:进去是测试运行环境;它构造带 session 和 always persist 元数据的空 schema 请求并渲染;出来是快照断言。

调用关系:它覆盖 approval_supports_persist_mode 对普通审批 UI 的影响。

调用图:调用 2 个内部函数(default, new);外部调用 6 个(assert_snapshot!, json!, empty_object_schema, form_request, from_form_request, test_sender)。

tests::approval_form_tool_approval_with_persist_options_snapshot2651–2678 ↗
fn approval_form_tool_approval_with_persist_options_snapshot()

作用:测试工具审批带会话/永久允许选项时的画面。它保证工具审批的持久化文案正确。

数据流:进去是测试运行环境;它构造支持持久化的工具审批请求并渲染;出来是快照断言。

调用关系:它覆盖 from_parts 生成工具审批持久化选项和 render。

调用图:调用 2 个内部函数(default, new);外部调用 6 个(assert_snapshot!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta)。

tests::approval_form_tool_approval_with_param_summary_snapshot2681–2731 ↗
fn approval_form_tool_approval_with_param_summary_snapshot()

作用:测试工具审批显示参数摘要时的画面。它保证用户能看到关键参数,并且长参数会被截断。

数据流:进去是测试运行环境;它构造带多个显示参数的工具审批请求并渲染;出来是快照比较。

调用关系:它覆盖 parse_tool_approval_display_params、format_tool_approval_display_message 和 render 的组合效果。

调用图:调用 2 个内部函数(default, new);外部调用 8 个(assert_snapshot!, json!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta, vec!)。

tui/src/bottom_pane/request_user_input/render.rs源码 ↗
domain_logicmain loop / render frame

这个文件解决的是一个很实际的问题:程序在终端里问用户问题时,屏幕空间很小,问题、选项、备注输入框、进度提示、快捷键说明都不能乱挤。它先算出弹窗需要的高度,再画出统一的弹窗背景,然后按顺序放进度、问题文字、选项列表、备注输入框和底部提示。如果用户准备跳过但还有问题没答,它会切到一个“还有未回答问题”的确认界面。这里还特别注意了终端里的文字宽度,比如中文字符比英文宽,截断提示文字时不能随便按字节切,否则会显示错。可以把它想成一个小排版师:先量尺寸,再分区域,最后把每一行稳稳放到屏幕缓冲区里。

函数细节14
line_to_owned47–60 ↗
fn line_to_owned(line: Line<'_>) -> Line<'static>

作用:把一行可能借用别处文字的界面文本,变成自己持有文字的版本。这样后面保存和传递时,不会因为原来的临时文字消失而出问题。

数据流:进去的是一条带样式的 Line,里面可能有借来的字符串 → 它逐个复制每个 Span 的文字,同时保留颜色、粗体、对齐等样式 → 出来的是一条内容完全自有的 Line,可安全长期放在布局结构里。

调用关系:它是确认未答问题界面排版时的小帮手。RequestUserInputOverlay::unanswered_confirmation_layout 会先把标题、提示等文字自动换行,再用它把这些行变成可保存的 owned 版本。

RequestUserInputOverlay::desired_height63–106 ↗
fn desired_height(&self, width: u16) -> u16

作用:告诉外层界面:这个提问弹窗理想情况下需要多高。这样终端底部能提前给它留出合适空间,避免内容被压坏或看不全。

数据流:进去的是可用宽度 width,并读取当前问题、选项、备注框、底部提示、是否处于“确认未答问题”状态 → 它先算弹窗内部宽度,再分别估算问题、选项、备注、页脚和边框占多少行 → 出来的是一个高度数字,并保证不会低于最小高度。

调用关系:这是 Renderable 接口的一部分,外层布局系统会在绘制前问它要多大。如果当前是未答确认界面,它会把计算交给 RequestUserInputOverlay::unanswered_confirmation_height;普通界面则会用 menu_surface_inset 和 menu_surface_padding_height 来配合通用弹窗外壳。

调用图:调用 3 个内部函数(unanswered_confirmation_height, menu_surface_inset, menu_surface_padding_height);外部调用 1 个(new)。

RequestUserInputOverlay::render108–110 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

作用:真正开始绘制这个弹窗。它是外层渲染系统调用的标准入口。

数据流:进去的是屏幕上的矩形区域 area 和要写入的终端缓冲区 buf → 它取当前时间 Instant::now,用来显示倒计时之类的实时内容 → 然后把具体绘制工作交给 RequestUserInputOverlay::render_ui_at。

调用关系:这是 Renderable 接口要求的函数。它自己不排版,只负责把“现在是什么时间”补上,然后进入 render_ui_at 这个主绘制流程。

调用图:调用 1 个内部函数(render_ui_at);外部调用 1 个(now)。

RequestUserInputOverlay::cursor_pos112–114 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

作用:告诉终端光标应该放在哪里,主要用于用户正在输入备注时。没有可编辑输入框时,它会说“不显示光标”。

数据流:进去的是弹窗所在区域 area → 它不直接计算,而是把区域交给 RequestUserInputOverlay::cursor_pos_impl → 出来的是一个坐标,或者 None 表示此时不需要显示光标。

调用关系:这是 Renderable 接口给外层光标系统用的入口。真正的判断,比如焦点是不是在备注框、备注框是否可见,都在 cursor_pos_impl 里完成。

调用图:调用 1 个内部函数(cursor_pos_impl)。

RequestUserInputOverlay::unanswered_confirmation_data118–131 ↗
fn unanswered_confirmation_data(&self) -> UnansweredConfirmationData

作用:准备“还有问题没回答,确定继续吗”这个确认弹窗需要的原始内容。包括标题、副标题、提示行、选项行和滚动状态。

数据流:它读取当前还有多少未答问题、未答确认框的滚动状态,以及要展示的确认选项 → 组装标题、类似“3 unanswered questions”的副标题、标准快捷键提示和选项列表 → 出来的是 UnansweredConfirmationData,供后续排版使用。

调用关系:它被 RequestUserInputOverlay::unanswered_confirmation_layout 调用。也就是说,它只负责备料;换行、测量和最终绘制不在这里做。

调用图:调用 1 个内部函数(standard_popup_hint_line);被 1 处调用(unanswered_confirmation_layout);外部调用 2 个(from, format!)。

RequestUserInputOverlay::unanswered_confirmation_layout133–150 ↗
fn unanswered_confirmation_layout(&self, width: u16) -> UnansweredConfirmationLayout

作用:把未答确认弹窗的内容整理成适合当前宽度显示的布局。宽了少换行,窄了多换行。

数据流:进去的是内容宽度 width → 它先调用 RequestUserInputOverlay::unanswered_confirmation_data 拿到标题、提示和列表,再用 wrap_styled_line 按宽度换行,并把文字变成自有版本 → 出来的是 UnansweredConfirmationLayout,里面有标题行、提示行、列表行和滚动状态。

调用关系:它是未答确认界面的排版中转站。RequestUserInputOverlay::unanswered_confirmation_height 用它来量高度,RequestUserInputOverlay::render_unanswered_confirmation 用它来真正画到屏幕上。

调用图:调用 2 个内部函数(unanswered_confirmation_data, wrap_styled_line);被 2 处调用(render_unanswered_confirmation, unanswered_confirmation_height)。

RequestUserInputOverlay::unanswered_confirmation_height152–170 ↗
fn unanswered_confirmation_height(&self, width: u16) -> u16

作用:计算未答确认弹窗需要多高。这样界面可以给这个特殊确认框留出足够空间。

数据流:进去的是可用宽度 width → 它算出弹窗内部宽度,调用 RequestUserInputOverlay::unanswered_confirmation_layout 得到换行后的内容,再用 measure_rows_height 测量选项列表需要几行,最后加上标题、空行、提示和弹窗边距 → 出来的是高度数字,并保证不低于最小高度。

调用关系:它只在 RequestUserInputOverlay::desired_height 发现当前处于未答确认状态时使用。它和 render_unanswered_confirmation 用的是同一套 layout,所以“算出来的高度”和“实际画出来的内容”能对得上。

调用图:调用 4 个内部函数(unanswered_confirmation_layout, measure_rows_height, menu_surface_inset, menu_surface_padding_height);被 1 处调用(desired_height);外部调用 1 个(new)。

RequestUserInputOverlay::render_unanswered_confirmation172–246 ↗
fn render_unanswered_confirmation(&self, area: Rect, buf: &mut Buffer)

作用:把“还有未回答问题”的确认界面画出来。这个界面和普通提问界面不同,重点是提醒用户还有问题没答,并给出可选操作。

数据流:进去的是要绘制的区域 area 和终端缓冲区 buf → 它先画通用弹窗外壳,拿到内部可用区域,再获取未答确认布局;随后从上到下画标题、副标题、空行、选项列表和底部提示 → 结果是 buf 里对应区域被写成确认弹窗画面。

调用关系:它被 RequestUserInputOverlay::render_ui_at 在检测到 confirm_unanswered_active 时调用。它使用 render_menu_surface 画外壳,用 render_rows 画可滚动选项列表。

调用图:调用 3 个内部函数(unanswered_confirmation_layout, render_menu_surface, render_rows);被 1 处调用(render_ui_at);外部调用 2 个(new, from)。

RequestUserInputOverlay::render_ui_at248–380 ↗
fn render_ui_at(&self, area: Rect, buf: &mut Buffer, now: Instant)

作用:这是普通提问弹窗的主绘制流程。它把整个弹窗从空白区域一步步画成用户看到的界面。

数据流:进去的是绘制区域 area、终端缓冲区 buf 和当前时间 now → 它先检查区域是否可用;如果是未答确认模式,就转去 RequestUserInputOverlay::render_unanswered_confirmation;否则画弹窗外壳,分配进度区、问题区、选项区、备注区、页脚区,再逐块写入内容 → 出来时 buf 已经包含完整弹窗画面,包括倒计时、当前问题、选项、备注输入框和底部快捷键提示。

调用关系:RequestUserInputOverlay::render 会调用它。它是这个文件的总调度者:需要备注框时调用 RequestUserInputOverlay::render_notes_input,需要选项列表时调用 render_rows_bottom_aligned,需要截短页脚提示时调用 truncate_line_word_boundary_with_ellipsis;如果进入未答确认分支,则交给 render_unanswered_confirmation。

调用图:调用 6 个内部函数(new, render_notes_input, render_unanswered_confirmation, render_rows_bottom_aligned, truncate_line_word_boundary_with_ellipsis, render_menu_surface);被 1 处调用(render);外部调用 5 个(from, new, new, format!, vec!)。

RequestUserInputOverlay::cursor_pos_impl383–406 ↗
fn cursor_pos_impl(&self, area: Rect) -> Option<(u16, u16)>

作用:计算备注输入框里的光标坐标。它会先确认当前真的应该显示光标,避免光标出现在选项列表或确认弹窗里。

数据流:进去的是弹窗区域 area,并读取当前是否处于未答确认、是否有选项、备注框是否可见、焦点是否在备注框 → 如果任何条件不合适,就返回 None;如果合适,它算出弹窗内部区域和备注输入区,再问 composer 光标在输入框里的位置 → 出来的是屏幕坐标或 None。

调用关系:它被 RequestUserInputOverlay::cursor_pos 调用。它依赖普通界面的 layout_sections 来找到备注框位置,并最终把具体输入文字内的光标计算交给 composer。

调用图:调用 1 个内部函数(menu_surface_inset);被 1 处调用(cursor_pos)。

RequestUserInputOverlay::render_notes_input409–421 ↗
fn render_notes_input(&self, area: Rect, buf: &mut Buffer)

作用:绘制用户填写备注的输入框。如果当前问题是秘密内容,它会用星号遮住输入,像密码框一样。

数据流:进去的是备注框区域 area 和缓冲区 buf → 它先检查区域是否为空,再查看当前问题是否标记为 secret → 普通问题直接让 composer 渲染文字;秘密问题让 composer 用 '*' 掩码渲染 → 结果是备注输入框被画到 buf 中。

调用关系:它由 RequestUserInputOverlay::render_ui_at 在备注区域可见时调用。它不自己管理输入内容,只负责根据是否保密选择 composer 的两种绘制方式。

调用图:被 1 处调用(render_ui_at)。

line_width424–428 ↗
fn line_width(line: &Line<'_>) -> usize

作用:计算一整行文字在终端里实际占多少列。它不是简单数字符,因为中文、表情等字符通常比英文字母更宽。

数据流:进去的是一条带多个 Span 的 Line → 它逐段读取文字,用 unicode_width 计算每段在终端里的显示宽度,然后相加 → 出来的是这一行会占用的列数。

调用关系:它被 truncate_line_word_boundary_with_ellipsis 调用。后者需要先知道一行是否太长,才能决定要不要截断并加省略号。

调用图:被 1 处调用(truncate_line_word_boundary_with_ellipsis);外部调用 1 个(iter)。

render_rows_bottom_aligned435–470 ↗
fn render_rows_bottom_aligned(
    area: Rect,
    buf: &mut Buffer,
    rows: &[crate::bottom_pane::selection_popup_common::GenericDisplayRow],
    state: &ScrollState,
    max_results: usize,
    em

作用:把选项列表画在分配区域的底部,而不是总是从顶部开始。这样当选项很少时,底部提示的位置看起来更稳定,不会被上方空白挤得怪怪的。

数据流:进去的是目标区域 area、主缓冲区 buf、要显示的行 rows、滚动状态 state、最多结果数和空列表提示 → 它先建一个临时缓冲区,把原区域内容复制进去,再调用 render_rows 在临时区画列表;画完后计算实际用了多少行,并把这些行贴回主缓冲区的底部 → 出来时主缓冲区里目标区域底部显示了列表。

调用关系:它被 RequestUserInputOverlay::render_ui_at 用来显示普通问题的选项。真正的列表绘制仍交给共享函数 render_rows,它只是在外面加了一层“底部对齐”的摆放规则。

调用图:调用 1 个内部函数(render_rows);被 1 处调用(render_ui_at);外部调用 2 个(empty, new)。

truncate_line_word_boundary_with_ellipsis479–578 ↗
fn truncate_line_word_boundary_with_ellipsis(
    line: Line<'static>,
    max_width: usize,
) -> Line<'static>

作用:把太长的底部提示行截短,并在末尾加省略号。它尽量从单词边界切开,避免把一句话切得太难看。

数据流:进去的是一条带样式的 Line 和最大显示宽度 max_width → 它先用 line_width 判断是否已经放得下;如果放不下,就按字符真实显示宽度逐个扫描,记住最后一个能放下的位置和最后一个空白断点;最后裁掉多余文字、去掉尾部空格、追加“…”并保留合适的样式 → 出来的是一条不会超宽的 Line。

调用关系:它被 RequestUserInputOverlay::render_ui_at 用在页脚提示上。页脚里可能有很多快捷键说明,窗口窄时必须靠它安全截断,避免文字溢出或把宽字符切坏。

调用图:调用 1 个内部函数(line_width);被 1 处调用(render_ui_at);外部调用 6 个(from, styled, width, width, new, new)。