拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CodeWhale 设置选择器框架(Settings Picker Framework)全解析:从 /theme 到 ConfigView 的统一设置交互层

CodeWhale 设置选择器框架Settings Picker Framework全解析从 /theme 到 ConfigView 的统一设置交互层【免费下载链接】CodewhaleOpen-source coding agent for your terminal, built in Rust and on a journey of continuous community improvement. Issues and PRs welcome.项目地址: https://gitcode.com/GitHub_Trending/de/Codewhale本指南围绕 CodeWhale 终端 TUI 中的共享设置选择器框架展开说明 Option 目录、键盘导航、预览/提交/回滚事务生命周期与响应式布局这套共享契约如何沉淀在crates/tui/src/tui/settings_picker/并剖析其完整落地形态 —— 迁移完成的内置主题选择器/theme以及类型化的全量设置编辑器ConfigView。读完你将掌握该框架的接入 API、过滤与选中语义、键盘映射、窄终端布局策略以及为/model、/provider等具体选择器完成迁移所需的全部实现细节。一、框架定位共享契约与海洋主题视觉的边界设置选择器框架是 CodeWhale 水下主题 TUIunderwater TUI中所有具体选择器theme / model / provider / config共用的基础设施其完整契约与集成说明见 docs/SETTINGS_PICKER_FRAMEWORK.md核心实现集中在 crates/tui/src/tui/settings_picker/ 下的四个文件option.rs—— 声明式的选项数据契约SettingOption、SettingValues、SettingAvailability、SettingItemActioncontroller.rs—— Tab 搜索过滤与稳定可见索引的所有者SettingsPickerController以及导航/提交结果枚举PickerNavResultlayout.rs—— 宽窄终端的列表/详情响应式几何SettingsPickerLayoutmod.rs—— 模块导出与键盘到导航契约的映射函数handle_nav_key。模块顶部契约注释crates/tui/src/tui/settings_picker/mod.rs明确了框架的职责边界框架拥有Option 目录 Tab/搜索过滤带稳定的可见索引、键盘导航↑/↓/Home/End/数字键/Tab、带原因的禁用行、可选的每行次级操作per-item action、通过PickerNavResult表达的预览/提交/取消生命周期、宽屏并排/窄屏堆叠或仅列表的响应式布局。框架不拥有海洋主题视觉Ocean chrome。色板条swatches、水下表面绘制、本地化文案都保留在具体选择器里以免共享契约把各选择器的视觉个性抹平 —— 这正是 crates/tui/src/tui/theme_picker.rs 中强调的具体 picker 位于模块之上的分层结构。因此框架对外暴露的是一份纯数据选项列表 一个控制器 一套导航函数 一种布局算法而任何界面观感都由宿主选择器自行负责。二、选项目录声明一份可预览、可禁用、可携带动作的设置项每个可选项在框架中是一个SettingOption其结构体定义在 crates/tui/src/tui/settings_picker/option.rs。它不仅是名字 取值还携带了真实呈现truthful chrome所需的全部元信息。2.1 字段总览字段类型含义idCowstatic, str行唯一标识用于提交/回滚的身份还原labelCowstatic, str列表中展示的主名称summaryCowstatic, str列表行内的短副行tagline / 一句话说明detailCowstatic, str详情窗格中的较长说明helpCowstatic, str帮助文案valuesSettingValuesCowstr三重取值current / default / effectiveavailabilitySettingAvailabilityAvailable或带原因的Disabled { reason }tabCowstatic, str所属分组默认all控制器会自动聚合成标签页actionOptionSettingItemAction可选的行级次级操作如显示色板 swatchprefer_list_when_narrowbool窄终端时是否倾向仅保留列表、丢弃详情窗默认true2.2 三重取值 SettingValuesSettingValuesToption.rs同时持有current、default、effective三个值目的是让界面诚实地区分三件事当前保存的值是什么、默认值是什么、在当前环境与叠加配置下真正生效的值是什么。以/theme选择器的每一行为例theme_picker.rsSettingValues::new( Cow::Owned(current.clone()), // current打开 picker 时已保存的选择器名 Cow::Borrowed(underwater), // defaultreset 应回到的水下默认主题 Cow::Borrowed(name), // effective把这一行选上后实际生效的主题名 )注意默认值被特意固定为underwater而不是检测出的系统主题注释theme_picker.rs说明这是为了避免 reset 后界面被一次按环境检测出的配色重新粉刷。2.3 禁用与原因、可选的每行动作SettingAvailabilityoption.rs只有两个分支Available与Disabled { reason }。禁用行仍参与列表渲染与计数只是不能被选中、预览或提交 —— 这在模块测试中被固定下来controller.set_query(locked); assert_eq!(controller.move_down(), PickerNavResult::None); assert_eq!(controller.request_commit(), PickerNavResult::None);即对禁用行move_down与request_commit都只会返回PickerNavResult::None详情窗格则能展示禁用原因文案参见 crates/tui/src/tui/settings_picker/mod.rs 的矩阵测试。源码中该字段标注为为 model/provider picker 预留禁用行TUI-DOG-009。SettingItemAction { id, label }option.rs是挂在某一行上的次级按钮如刷新“查看色板”通过空格键触发返回PickerNavResult::ItemAction最终由宿主决定如何响应。2.4 Fluent Builder框架提供了SettingOption::builder(id, label)链式构造器option.rs所有字段都有显式默认值tab默认all、availability默认Available、prefer_list_when_narrow默认true、文本类字段默认空串。下面是在模块自带测试中实际使用的构造示例mod.rs 的测试use std::borrow::Cow; use crate::tui::settings_picker::{ SettingAvailability, SettingItemAction, SettingOption, SettingValues, }; // 一个普通的可用选项 SettingOption::builder(dracula, Dracula) .summary(Purple night) .detail(Classic Dracula palette.) .help(Popular dark theme) .values(SettingValues::new( Cow::Borrowed(dracula), Cow::Borrowed(system), Cow::Borrowed(dracula), )) .tab(extra) .action(SettingItemAction { id: Cow::Borrowed(swatch), label: Cow::Borrowed(Show swatch), }) .build(); // 一个禁用行列出原因且窄终端下倾向只显示列表 SettingOption::builder(locked, Locked Theme) .availability(SettingAvailability::Disabled { reason: Cow::Borrowed(requires fancy_animations), }) .tab(extra) .prefer_list_when_narrow(true) .build();三、SettingsPickerController过滤、选中身份与稳定索引控制器是整份 Option 目录、搜索词、Tab 过滤以及可见行 → 源索引映射的唯一所有者。controller.rs 的模块注释 明确约定调用方绝不在渲染期间临时自行重新过滤只能读取visible()。这是保证索引稳定与无滚动跳变的关键。3.1 构造与内部状态pub fn new(options: VecSettingOption, original_id: impl IntoString) - Self构造时controller.rs会自动生成标签页列表首个固定为all随后按各 option 的tab字段去重追加首次计算过滤结果当original_id非空时优先把光标落在原始 id 对应的行上—— 这正是打开 picker 时定格在当前生效项的实现基础ThemePickerView通过它落在已持久化主题上测试opens_at_persisted_theme验证了这一点。内部关键字段包括filtered: Vecusize通过当前 Tab 搜索的行在options中的源索引、selected_visible: usize指向filtered的偏移而非 options 偏移、以及用于回滚的original_id快照。3.2 过滤语义搜索跨全部标签页recompute_filtercontroller.rs实现了三条重要规则空搜索时按当前 Tab 过滤all标签不过滤搜索非空时跨全部标签页匹配范围覆盖id、label、summary、detail均做小写比较这样操作者输入过滤词时不会被困在当前 Tab 里 —— 注释明确写明这一设计意图过滤后若传入prefer_source_id则在命中列表里找回该 id 对应行并定位光标否则把selected_visible夹紧到新列表范围内。身份保持是过滤的核心 UX无论是push_query_char逐字符输入、pop_query_char退格、clear_queryCtrlU还是set_active_tab切换 Tab都会先记住当前选中行的 id重算后再把光标恢复到该行。矩阵测试matrix_filtered_preserves_selection_identitymod.rs验证了输入足够具体的查询后选中身份不被冲掉这一行为。3.3 移动语义环形越界与禁用行拦截移动类方法move_up/move_down/jump_home/jump_end/jump_digit都返回PickerNavResult上/下移动在可见列表两端回绕(selected_visible 1) % filtered.len()对应测试arrow_navigation_wraps_at_picker_edges验证了↑ 到顶后落到最后一行移动目标若不可用禁用行返回None而不是Preview——preview_if_availablecontroller.rs统一处理数字跳转jump_digit只接受1..9且不越界0被明确拒绝而不是重映射到第 0 行测试digit_zero_does_not_jump与digit_zero_is_rejected_not_remapped_to_row_zero双保险验证request_commit只在当前选中行可用时返回Commit否则Nonerequest_item_action只在行带 action 且可用时返回ItemAction。四、键盘导航handle_nav_key 的完整映射handle_nav_keycrates/tui/src/tui/settings_picker/mod.rs把一次KeyEvent翻译成导航契约完整映射如下按键行为返回Esc请求取消宿主回滚并关闭CancelEnter请求提交仅当选中行可用Commit/NoneTab下一个标签页PreviewShiftTab/BackTab上一个标签页Preview↑/k无 Ctrl/Alt 修饰上一行环形Preview/None↓/j无 Ctrl/Alt 修饰下一行环形Preview/NoneHome/End跳到可见列表首/末Preview/None1–9无修饰跳转到可见列表第 N 行Preview/None其他字符无修饰、非j/k/1..9搜索输入若allow_search_typing开启NoneBackspace弹出搜索字符NoneCtrlU清空搜索NoneSpace触发当前行次级动作ItemAction/None其余忽略None两个关键设计点值得展开1)allow_search_typing由宿主决定而不是框架硬编码。模块注释mod.rs说明当开关为true时字符搜索输入j/k、1..9、vim 键以外的字符交给宿主逻辑处理当为false时j/k与数字键保留其导航语义。/theme主题选择器正是关闭了搜索输入见 theme_picker.rs 的 handle_key让j/k的 vim 移动与1-9数字直跳继续生效而计划承载搜索过滤的 model/provider 选择器可以开启它。测试matrix_preview_commit_and_revert_sequence以false调用验证了纯键盘的预览/提交/回滚路径。2)CtrlU仅在allow_search_typing开启时有效避免在不需要搜索的 picker 中吞掉常见编辑键。五、PickerNavResult预览 → 提交 → 取消 的事务生命周期框架把导航的每一种结果收敛进一个四值外加 No-op枚举PickerNavResultcontroller.rspub enum PickerNavResult { Preview, // 选中变化宿主应发出 persist:false 的预览 Commit, // 在可用项上按 Enter宿主应提交persist:true并关闭 Cancel, // Esc / 显式取消宿主应回滚到 original_id 并关闭 ItemAction, // 聚焦行上的次级动作 None, // 空操作禁用行、空列表、未知按键 }值得强调的设计哲学框架不直接写配置。选项列表是纯数据宿主把PickerNavResult映射到自己的ViewAction/事件从而同一份选项列表既能驱动实时预览又不必改变提交策略见 option.rs 模块注释。5.1 框架文档中的最小接入示例框架文档给出了如下接线骨架它忠实地把三个结果分别映射到不持久化预览 / 持久化并关闭 / 回滚并关闭use crate::tui::settings_picker::{ SettingOption, SettingsPickerController, SettingsPickerLayout, handle_nav_key, PickerNavResult, }; let mut controller SettingsPickerController::new(options, original_id); let result handle_nav_key(mut controller, key, /* allow_search_typing */ true); match result { PickerNavResult::Preview { /* emit persist:false preview */ } PickerNavResult::Commit { /* emit persist:true and close */ } PickerNavResult::Cancel { /* rollback close */ } _ {} } let layout SettingsPickerLayout::resolve(area, 34, controller.selected_option());5.2 落地参照/theme 主题选择器的三方映射已完成迁移的ThemePickerView是最佳范本。它把三个结果分别翻译成ViewActionPreview → Emit(ThemeSelectionUpdated { theme, persist: false })宿主立即换掉app.ui_theme让整个 TUI 在模态窗底下即时重绘hover 与滚轮同样走此路径测试hover_moves_highlight_and_previews_without_persisting覆盖Commit → EmitAndClose(ThemeSelectionUpdated { persist: true })Cancel → EmitAndClose(ThemeSelectionUpdated { original_theme_name, persist: false })Esc 会精确恢复到打开 picker 那一刻的主题测试esc_reverts_to_exact_original_theme验证回滚到dracula。映射实现见 theme_picker.rs 的 action_from_nav / commit_event / revert_event。其中commit_event还有一个细节若光标从未离开过打开位置selected_source_index() opening_cursor提交会保留原始选择器字符串—— 这保护了custom:name这类没有对应编译主题行的选择器即使其主题文件在 picker 打开期间消失或失效也不会被误替换成某一行编译主题测试enter_without_navigating_preserves_a_custom_theme_selector。主题选项列表的构造同样值得参考theme_picker.rs 的 theme_options_with_custom先按SELECTABLE_THEMES顺序生成编译主题行带各自 tagline再把有效的用户叠加主题custom:前缀追加为普通行underwater与自定义叠加只是普通的一行—— 被粉刷的海面本身就是主题而不是主题旁的一种装饰处理。六、SettingsPickerLayout宽窄终端的响应式几何布局由 crates/tui/src/tui/settings_picker/layout.rs 提供。resolve(area, min_detail_width, focused)复用 views 中的共享ListDetailLayout切分逻辑产出pub struct SettingsPickerLayout { pub list: Rect, pub detail: OptionRect, // None 表示本帧不画详情窗 pub stacked: bool, // 详情窗堆叠在列表下方 pub narrow: bool, // 当前判为窄屏 }判定与回退规则layout.rs 的 resolve空区域保护area宽或高为 0 时直接返回仅列表、stacked、narrow的空布局避免后续滚动数学下溢 —— theme picker 的测试render_does_not_panic_on_zero_sized_area与render_does_not_panic_on_tiny_area正是为历史上曾在零尺寸区域 panic 的回归所写窄屏判定底层切分结果本身为 stacked或宽度 96列均判为 narrowprefer_list_when_narrow 回退若判为窄屏且聚焦选项设置了prefer_list_when_narrow则丢弃详情窗、只保留列表。主题行全部设置了该偏好因此主题选择器在窄终端不会压缩详情而框架测试矩阵里也验证了普通选项在窄屏会走列表在上、详情堆叠在下的 stacked 形态matrix_normal_layout_is_side_by_side断言宽屏narrowfalse且detailtruematrix_narrow_falls_back_to_list_only_when_preferred断言窄屏 prefer_list 时detailfalse。这意味着框架文档示例中的34是详情窗最小宽度列参数宿主可按自己详情内容的排版需要调整主题 picker 渲染时同样用SettingsPickerLayout::resolve(content, 34, selected_option())驱动滚动区计算theme_picker.rs。七、迁移状态framework 应该被谁用、何时用框架文档给出了明确的迁移状态表源码中也有相应标注Picker状态说明/theme已迁移使用SettingsPickerControllerhandle_nav_key保留色板与实时预览/modelHook ready完整迁移交给 TUI-DOG-009 相关改动真实性可用性/性能勿重复实现/providerHook ready同上在可用性相关工作进行中不要重写Fleet 设置向导Framework only流程重写归 billing/Fleet UX 相关改动所有框架文档对迁移给出两条明确的工程纪律model / provider调用SettingsPickerController::new(options, original_id)把PickerNavResult映射进既有ViewAction并用SettingsPickerLayout::resolve取代临时手写的 split —— 注意这些 picker 的宿主接口对部分访问器打了#[allow(dead_code)]标注并注明为 model/provider 迁移TUI-DOG-009预留见 controller.rsFleet 设置向导目前只有框架层宿主流程重写尚未接入草案就绪后接入控制器即可。而模块注释中的集成钩子清单mod.rs补充了一条关键边界主题的预览/回滚仍走它既有的ViewAction通道宿主自行把PickerNavResult翻译成自己的动作 —— 框架不假设宿主的事件机制。八、矩阵级单元测试六种形态全覆盖框架文档提到matrix coverage lives in settings_picker unit tests。这些测试集中在 mod.rs 内嵌测试模块用一份 4 行样本选项system / terminal / locked / dracula跨core与extra两个标签页做快照矩阵测试覆盖形态关键断言matrix_normal_layout_is_side_by_side宽屏正常态narrowfalse、detailtrue、首行 System 带 currentsystem 详情matrix_narrow_falls_back_to_list_only_when_preferred窄屏 prefer_listnarrowtrue、detailfalsematrix_disabled_row_blocks_preview_and_commit禁用行过滤只剩 1 行、move/commit 均None、详情含禁用原因matrix_filtered_preserves_selection_identity过滤状态输入精确查询后仍选中原 idterminalmatrix_preview_commit_and_revert_sequence完整事务Down→Preview、Enter→Commit、Esc→Cancel且original_id仍为 systemdigit_zero_does_not_jump/item_action_fires_on_space键位边界0不移动光标空格触发 dracula 行的swatchactionbacktab_uses_the_same_previous_tab_path_as_shift_tabTab 回退BackTab与ShiftTab同路径回到最后一个标签页此外在宿主侧 theme_picker.rs 的测试 还覆盖了打开即落在已持久化主题、未知主题名回退首行、方向键实时预览、滚轮预览 第二次点击提交、hover 跟随预览、Esc 精确回滚、数字跳转、每个可选中主题都能走同一渲染表面渲染、窄屏 tagline 语义截断不溢出、以及四种阻塞器尺寸80×24 / 100×30 / 120×32 / 160×40下背景无透传、页脚可操作、无行溢出并有按CODEWHALE_BLESS_GOLDENS1再生成的逐单元格 golden 测试。这些测试合起来就是一份框架契约的可执行规格。九、类型化设置编辑器 ConfigView一行一行读、一列一列改框架文档指出完整应用的设置编辑器是 crates/tui/src/tui/views/mod.rs 中的ConfigView入口方式包括快捷键F2裸命令/settings与/config命令发现command discovery机制。在命令层SETTINGS_INFO注册为usage: /settings [text]见 crates/tui/src/commands/groups/config/mod.rs框架文档补充说明/settings text保留旧的纯文本诊断视图用于 headless 与兼容性场景 —— 也就是说裸/settings打开类型化编辑器而带text参数仍走遗留文本输出两类消费者各取所需。9.1 SettingsRegistry六种行类型的单一分类法ConfigView的每一行都对应一条类型化元数据由SettingsRegistry::metaviews/mod.rs按优先级判定类型SettingKind判定条件交互方式ReadOnly!row.editable如会话诊断、托管策略行只读展示Action行携带facts.command激活后执行命令如打开 provider/model pickerBooleanconfig_boolean_key(row.key)Space / Enter 切换Choice有可选值集合如reasoning_effort按当前路由 provider 动态计算打开 chooser 选择Integerconfig_integer_key(row.key)行内编辑器Text兜底行内编辑器其前置结构SettingKindviews/mod.rs上的注释点明了一条架构原则编辑器行为与值存储位置刻意解耦—— kind 只决定交互与校验表面而 category/scope 描述归属权。这正是类型化 Settings 编辑器与普通键值编辑的本质区别。9.2 行分类、归属与作用域SettingsRegistry在ConfigSection14 个细粒度分区Provider / Model / Permissions / Network / Display / Composer/Sidebar / History / Mcp / Fleet / Workflow / Session / Legacy / Experimental见 views/mod.rs之上再做一道七类视图导轨分类ConfigCategoryAppearance / ModelsProviders / Work / ToolsMcp / Trust / Motion / Advancedviews/mod.rs每个分类映射到 settings schema 的 tab id行仍保留其细粒度ConfigSection分类由行的 schemaui.tab决定兜底 Advanced。ConfigSection与ConfigCategory在ConfigView中被广泛使用例如ConfigCategory::for_row决定行的导轨归属ConfigRow携带 session / saved 作用域ConfigScope会话行来自当前路由快照saved 行来自持久化配置。以路由行为例views/mod.rsprovider、model行属于ConfigScope::Session且标注激活后打开/provider、/model完整 pickerSettingRowFacts的opens构造base_url、context_window等被建模为只读的会话诊断/存储收据—— 因为不能假装编辑一个正在运行的客户端的实时收据能改变它端点的修改只能通过/provider把凭据、模型、端点作为一个整体变更。而approval_policy/allow_shell等行会根据托管策略来源切换成只读托管行展示值 · 来源。9.3 编辑交互、过滤与鼠标镜像Boolean行用 Space 或 Enter 切换有界 Choice走 chooserprovider/model 动作行打开各自完整 picker框架文档明确指出这一层复用框架的 pickerinteger/text行用行内编辑器ConfigEdit维护缓冲、光标、全选与原始值供取消还原。在行内直接输入文本会过滤整个列表与共享 picker 的查询语义一致。鼠标路径镜像键盘路径ConfigView维护last_row_hitboxes、last_choice_hitboxes、last_editor_controls、last_rail_hitboxes、last_nav_controls等命中矩形集合views/mod.rs点击只有落在实际画出的矩形内才生效hover 只染色、不移动键盘选中。设置标签文案来自 locale 语言包ConfigSection::label/ConfigCategory::label都经tr()走MessageId而原始配置键在编辑/详情界面保持可见用于诊断与兼容。ConfigView的完整键盘/鼠标行为还包含EditorControl::Apply/Cancel、分类条溢出时 ‹ › 翻页标记等详见 views/mod.rs 中 ConfigView 的实现与测试 及文件后段 60 个用例覆盖类型过滤、作用域展示、行内编辑、导轨分类等场景。十、工程实践小结什么时候该引入框架综合上述可以把使用决策收敛为三条规则新做任何从一组离散选项里选一个设置的 TUI 表面主题、模型、提供商、入口选择等优先在SettingOption之上声明选项并接入SettingsPickerController让 Tab/搜索/身份保持/禁用行/环形移动免费获得并把PickerNavResult显式映射到自己的预览/提交/回滚事件 ——/theme是当前唯一完整迁移参照crates/tui/src/tui/theme_picker.rs 是最佳范本。需要宽屏并排、窄屏堆叠/仅列表的自适应时使用SettingsPickerLayout::resolve(area, min_detail_width, focused)而不是再手写一套Layout::split分支行级prefer_list_when_narrow可让主题这类详情可有可无的表面在窄终端自动退回纯列表。面对成百上千条跨类别的完整应用设置不逐条套SettingOption而是使用类型化编辑器ConfigView它以SettingsRegistry把每一行归类为 Boolean/Choice/Integer/Text/Action/ReadOnly 六种交互原语保留分类与 session/saved 作用域并把 provider/model 等打开完整 picker的行挂接到框架表面 —— 裸/settings或 F2、/config进入该编辑器/settings text保留纯文本诊断出口。在模型/提供商选择器完成 TUI-DOG-009 相关的可用性迁移之前不要在它们上面自行重写一套过滤与索引逻辑框架预留了控制器访问器与禁用行原因通道届时只需把选项目录接入SettingsPickerController并按既有ViewAction翻译PickerNavResult即可无需触碰共享契约本身。【免费下载链接】CodewhaleOpen-source coding agent for your terminal, built in Rust and on a journey of continuous community improvement. Issues and PRs welcome.项目地址: https://gitcode.com/GitHub_Trending/de/Codewhale创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门