
gpui-kit 文本子系统迁移实战gpui-base 中的 TextView、SelectableText 与选择复制机制【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit本篇围绕 gpui-kit 仓库中的实施计划文档 gpui-base TextView 与 Selectable Text 实现计划 展开。计划的核心目标是让gpui-base独立提供可选择的纯文本SelectableText以及完整的 HTML/Markdown 富文本组件TextView——包括开箱可用的默认样式、指针选择与剪贴板复制且默认不启用语法高亮。读完后你将理解这次组件下沉的分层架构、五步迁移任务的编排方式、TextViewStyle完整默认样式的契约设计以及窗口级选择协调器TextSelection的工作原理并能从源码验证该计划在当前仓库中的落地状态。背景与目标为什么要把 TextView 下沉到 gpui-basegpui-kit 是一个基于 GPUI 的 Rust GUI 组件库仓库中包含多个 crategpui-base基础层、gpui-component组件层等。在迁移之前完整的富文本TextViewHTML/Markdown 解析、排版、选择、复制只存在于组件层的crates/ui/src/text/中这造成两个问题一个只依赖gpui-base的应用无法渲染像样的富文本必须自行搭建文本组件和样式表组件层被迫承担 Markdown/HTML 解析器这类本应属于基础层的依赖而语言高亮能力tree-sitter 相关又与基础层无业务关系。计划文档给出的Goal非常明确Makegpui-baseindependently provide selectable plain text and the complete HTML/MarkdownTextView, including usable default styling, selection, and copying without enabling syntax highlighting by default.配套的设计文档是 gpui-base TextView and Selectable Text 设计规格它规定了分层原则gpui-base拥有TextView/TextViewState、文档节点、布局渲染、Markdown 与 HTML 解析含扩展/插件 API、链接/图片/列表/表格/代码块、选择手势与选中文字生成、TextViewStyle及其完整的中性default()以及一个小而值语义的SelectableText元素gpui-component不再保留独立的 TextView 实现只在原公开路径上重导出 Base API并可为走gpui_component::init初始化的应用追加主题适配器。全局约束迁移的红线计划文档列出了若干 Global Constraints这些约束直接决定了整个迁移的安全性边界约束说明TextViewStyle::default()必须完整可用只依赖gpui-base的应用不构造任何样式也能渲染可读富文本不改动gpui_base::Selectable受控状态 trait文本元素命名为SelectableText避免与控件的受控选中状态 API 混淆gpui-base永不依赖/导入gpui-component依赖方向单向component → base保留 HTML、Markdown、表格、图片、链接、代码块、插件、选择格式、滚动、max-lines 截断功能等价迁移默认不启用语法高亮语言/高亮器依赖留在组件层Base 只暴露注入钩子保留gpui_component::text现有公开构造器与主 builder API通过重导出保持源码兼容保留唯一的窗口级选择协调器TextSelection跨组件选择的一致性基础文件结构与五个任务的总体编排计划采用典型的 spec-driven 任务分解先建参考组件再搬家解析/状态再搬家渲染并定义默认样式然后用兼容门面替换组件层旧实现最后用可运行的示例证明 Base-only 可用性。文件结构一览来自计划文档新建crates/base/src/selectable_text.rs聚焦的纯文本Element与选择绘制将原crates/ui/src/text/模块树整体迁入crates/base/src/text/成为规范实现修改crates/base/src/lib.rs初始化并导出文本 API修改crates/base/Cargo.toml只接管 Markdown/HTML 解析器依赖不引入 tree-sitter 相关依赖或 feature用兼容重导出替换crates/ui/src/text/mod.rs测试通过后才删除已搬走的实现文件。从当前仓库结构看这套结构已完整落地crates/base/src/text/ 下存在document.rs、format/、inline.rs、inline_flow.rs、markdown_ext.rs、node.rs、selection.rs、selection_adapter.rs、state.rs、style.rs、text_view.rs、utils.rs等文件而crates/ui/src/text/已不存在组件层只剩 crates/component/src/text/mod.rs 这一兼容门面。Task 1先写失败测试再落地 SelectableText 参考组件计划的第一步是测试先行。在crates/base/src/selectable_text.rs中先写构造器与投影测试用TextSelectionLayer构造视觉根排版first selectable text从first内部拖选到selectable断言窗口级选中结果assert_eq!( cx.update(|window, cx| TextSelection::selected_text(window, cx)), first selectable );第二个根包含两个共享同一 handle、document_order分别为10与20的元素跨两个元素拖选后断言选中文字按视觉/文档顺序拼接第三个复制测试聚焦根节点、派发 Base 的 copy action断言cx.read_from_clipboard().unwrap().text()与TextSelection::selected_text一致。随后运行cargo test -p gpui-base selectable_text --lib预期因SelectableText尚未定义而编译失败再实现元素。计划中给出的公共 API 形态是pub struct SelectableText { id: ElementId, handle: OptionTextSelectionHandle, text: SharedString, styled_text: StyledText, document_order: u64, selection_color: OptionHsla, } impl SelectableText { pub fn new(id: impl IntoElementId, text: impl IntoSharedString) - Self; pub fn with_handle(id, handle: TextSelectionHandle, text) - Self; pub fn document_order(mut self, order: u64) - Self; pub fn text_style(mut self, style: TextStyle) - Self; pub fn selection_color(mut self, color: Hsla) - Self; }实现要点new通过Window::with_element_state以id为键缓存自动生成的TextSelectionHandlewith_handle则绕过本地状态直接使用外部 handle多个元素共享选择时的关键prepaint 阶段登记 hitbox、bounds、文本 bounds 与文档序paint 阶段更新一条TextSelectionRun、在绘制字形之前画投影出的选中区间并在选中内容变化时请求刷新。对照当前源码 crates/base/src/selectable_text.rs实际落地的公共 API 与计划一致new、with_handle、document_order、text_style参数为 Base 的TextStyleRefinement比计划更贴合 Base 的样式体系、selection_color并在 lib.rs 根部重导出pub use selectable_text::SelectableText。Task 2搬家解析、文档状态与选择适配层Task 2 把解析与状态整体迁入 Base具体搬运清单源文件component 层目标Base 层crates/ui/src/text/document.rscrates/base/src/text/document.rscrates/ui/src/text/format/crates/base/src/text/format/crates/ui/src/text/markdown_ext.rscrates/base/src/text/markdown_ext.rscrates/ui/src/text/selection.rscrates/base/src/text/selection.rscrates/ui/src/text/selection_adapter.rscrates/base/src/text/selection_adapter.rscrates/ui/src/text/state.rscrates/base/src/text/state.rscrates/ui/src/text/utils.rscrates/base/src/text/utils.rs依赖调整也写得很具体向 Base 添加、并从 UI 移除markdown { version 1.0.0, features [serde] } html5ever 0.27 markup5ever_rcdom 0.3.0当前 crates/base/Cargo.toml 中这三行依赖原样存在可以确认搬运结果与计划一致。测试侧计划要求把既有 Markdown/HTML 测试随模块一起迁移并新增每种格式一个的 Base-only 解析测试构造TextViewState、解析代表性输入、断言纯文本选择/文档输出覆盖标题、强调文本、链接文本、列表项、围栏代码块、表格单元格Markdown以及 h1、strong、锚点文本、ul/li、pre/code、表格、img alt 文本HTML。UTF-8 词边界测试双击/三击选词也原样保留在 Base。一个容易被忽视的细节HTML 内联样式用到的颜色解析被要求收拢为crates/base/src/text/format/html.rs内的私有辅助函数继续用 GPUI 的Hsla且不引入 UI 主题辅助如yellow(...)之类——这是保证 Base 零组件依赖的关键卫生规则。初始化方面crates/base/src/lib.rs需要pub mod text;并在gpui_base::init中调用text::init(cx)且重复init必须安全。当前 crates/base/src/text/mod.rs 中init的实现确实如此——它只委托给state::init(cx)而根部lib.rs在init流程里调用了text::init(cx)。Task 3搬家渲染、移除组件私有依赖、定义完整默认样式这是计划中技术含量最高的一环。搬运inline.rs、inline_flow.rs、node.rs、style.rs、text_view.rs之外还要定义TextViewStyle的完整默认样式契约它要接管所有此前在node.rs、inline.rs、text_view.rs里直接读cx.theme()的展示输入字段涵盖段间距、标题基准字号、标题字号回调、正文/弱化正文/链接/选区颜色、引用边框、行内代码、代码块、表格三件套容器/表头/单元格、水平分割线颜色、暗色标记等Default必须是稳定的中性亮色值且不得检查App、高亮器注册表或任何组件全局状态同时提供TextViewStyle::from_theme(gpui_base::Theme)给希望跟随 Base 语义 token 的调用方。计划要求 Base-only 的渲染测试只用gpui_base::init构造根节点、用TextSelectionLayer包裹渲染这两段内容并断言布局非零、默认样式字段非空、代码块默认无高亮 runTextView::markdown(markdown, # Heading\n\nA [link](https://example.com).\n\nrust\nfn main() {}\n\n\n| A | B |\n|---|---|\n| 1 | 2 |) .selectable(true)TextView::html(html, h1Heading/h1pstrongbody/strong a hrefhttps://example.comlink/a/pprecodecode/code/pre) .selectable(true)组件私有依赖的替换清单同样精确crate::tooltip::Tooltip换成可选的 title renderer 回调缺省即无浮层Icon/IconName装饰改为保留code_block_actions由调用方提供元素组件ScrollableElement换回 Base/GPUI 的滚动句柄与列表行为所有cx.theme()组件 token 查找替换为已解析的TextViewStyleLanguageRegistry、SyntaxHighlighter、缓存的HighlightTheme状态整体替换为一个存于TextView并经NodeContext透传的可选回调。两个显式的 opt-in 钩子pub fn link_title_rendererF, E(self, renderer: F) - Self where F: Fn(SharedString, mut Window, mut App) - E Send Sync static, E: IntoElement; pub fn code_block_highlighterF(self, highlighter: F) - Self where F: Fn(CodeBlock) - Vec(Rangeusize, HighlightStyle) Send Sync static;高亮回调通过CodeBlock::{code, lang}拿到代码与语言返回相对code()的字节区间非法区间start 超过 end、end 超过代码长度在传给StyledText前被丢弃没有回调时渲染未高亮的等宽代码。现有code_block_actions、table_actions、on_link_click、Markdown 扩展/插件、max-lines、选择格式、selectable 等 builder 签名全部保留。对照当前源码可以确认落地情况。crates/base/src/text/style.rs 中的TextViewStyle采用私有字段 with_*构造器 同名访问器的设计注释明确说明这是为了跨过gpui-base边界保持新增字段即加法变更而非破坏性变更默认值由ColorTokens::light()派生测试default_style_is_readable_without_an_application_theme正是计划中默认样式不依赖应用主题的验证code_block_highlighter与CodeBlock::code()/lang()存在于 crates/base/src/text/text_view.rs 与 crates/base/src/text/node.rsNode内部还维护了CachedCodeBlockHighlights缓存以避免重复调用高亮回调。Task 4用兼容门面替换 gpui-component 的 TextViewTask 4 的核心是先写兼容测试再删旧代码。计划要求先加编译测试把组件路径的值赋给 Base 路径的类型证明两者实为同一类型let component: gpui_component::TextView gpui_component::text::markdown(# compatible); let _: gpui_base::text::TextView component; let style: gpui_component::text::TextViewStyle Default::default(); let _: gpui_base::text::TextViewStyle style;并用组件路径实测.selectable(true)、.selection_format(SelectionFormat::Source)、.scrollable(true)、.max_lines(3)、.code_block_actions(...)、.table_actions(...)、.on_link_click(...)及 Markdown 插件 builder以证明门面覆盖主要 API 面。然后把实现模块替换为pub use gpui_base::text::*;一类的重导出用编译错误清单反推必须转为 Base 私有或显式适配器 API 的 crate 私有路径——不允许恢复重复的 UI 实现文件。组件主题适配器按计划在 Base 所有权不变的前提下实现。当前 crates/component/src/text/mod.rs 中的base_text_view_style在计划草案的四个颜色映射foreground、muted_foreground、link、selection基础上更进一步补充了code_background、border、表头前景、行内代码背景、圆角StyleRefinement与with_dark并通过TextViewDefaults::install(cx)安装。而语法高亮没有写进适配器——只有开启组件的tree-sitterfeature 时install_text_view_defaults才会追加一个component_code_block_highlighter它按语言缓存SyntaxHighlighter用InputEdit增量更新Rope再按HighlightTheme取出样式 run。这正是计划Base 与默认值保持独立、高亮由消费者显式注入的最终形态。该文件中的测试模块同样对应计划 Step 1 的兼容测试意图。窗口选择集成测试也按计划在 Task 4 Step 4 中搬到所有者Base-only 的测试留在 crates/base/src/textwindow_selection模块以#[cfg(test)]存在只验证SelectableText与组件路径TextView在同窗口内可跨选。Task 5证明 Base-only 可用并完成仓库迁移收尾任务要求给出可执行的参考示例与所有权审计在 Base showcase 中新增text_view.rs页面渲染TextView::markdown(...).selectable(true)与TextView::html(...).selectable(true)且不传.style(...)默认样式自检TextSelectionLayer只放在 showcase 窗口根一次而非每个视图一次在selectable_text.rs与text/mod.rs的文档注释中写出单元素形式与共享 handle 形式并说明应用需要调用gpui_base::init(cx)、在窗口/根层渲染一个TextSelectionLayer以获得跨组件选择与复制用审计命令确认所有权rg -n gpui_component|gpui-component crates/base应无命中rg -n crate::text::(document|format|inline|node|selection|state) crates/ui/src应无命中find crates/ui/src/text只剩兼容门面格式化与全量验证cargo fmt --all -- --check、cargo test -p gpui-base、cargo test -p gpui-component text:: --lib、cargo check -p gpui-base --all-features、cargo check -p gpui-component --all-features最后cargo check --workspace --all-targets确保下游导入、feature 转发、WASM stub、示例与 story 无回归。当前仓库中crates/base/examples/showcase/components/text_view.rs 与 text_selection.rs 均已存在前者还包含text_view_showcase_renders_with_base_defaults一类的视觉测试直接验证只调gpui_base::init就能渲染默认样式 Markdown这一最终目标。计划之外的演进TextViewDefaults 全局默认值得注意的是当前实现比计划文档多出一块计划里没有的机制crates/base/src/text/text_view.rs 顶部的TextViewDefaults一个Global状态。它支持with_style与with_code_block_highlighter通过install(cx)安装、global(cx)读取——这意味着宿主应用如 gpui-component 的主题初始化可以在全局层面预置样式与高亮器而每个TextView仍可通过 builder 覆盖。这是默认不启用高亮、但组件宿主可整体接管这一设计原则的干净落点Base 提供注入通道宿主决定注入什么。落地后的公共 API 速览迁移完成后开发者直接面向的 API均可在 crates/base/src/lib.rs 的根部重导出中找到API用途gpui_base::markdown(source)/gpui_base::html(source)以代码位置自动生成 id 的便捷构造器#[track_caller]见 text/mod.rsTextView::markdown(id, src)/TextView::html(id, src)/TextView::new(state)显式 id 构造或直接绑定TextViewState做流式/受控更新.selectable(bool)/.selection_format(SelectionFormat)启用选择SelectionFormat::Plain默认复制渲染文本或Source全选复制原始源码、部分选择按节点重构为 Markdown见 state.rs.scrollable(bool)/.max_lines(n)内部滚动与按整行边界截断.style(TextViewStyle)/TextViewStyle::from_theme(Theme)覆盖默认样式或从 Base 语义 token 派生.code_block_actions(...)/.table_actions(...)/.on_link_click(...)注入组件层元素Base 自身不依赖 Icon/Tooltip.markdown_extensions(...)/.markdown_block_parser(...)/.markdown_block_renderer(...)/.plugin(...)Markdown 扩展、自定义块解析/渲染、TextViewPlugin钩子SelectableText::new(id, text)/with_handle(id, handle, text)纯文本选择元素共享 handle 时跨元素合并选择TextSelection/TextSelectionLayer/TextSelectionHandle窗口级选择服务根层渲染一个TextSelectionLayerTextSelection::selected_text(window, cx)读取当前选中完成审计如何验证迁移真的成功计划文档结尾的 Completion audit 给出了一份证据清单也是这篇文章可复用的验收标准rg证明规范的 TextView 实现只存在于 BaseBase-only 测试用TextViewStyle::default()实例化 Markdown 与 HTML 并绘制Base 测试对SelectableText完成指针选择与剪贴板复制断言跨注册测试证明选择可以穿过SelectableText与TextView组件兼容测试继续走旧公开路径与 builderrg证明 Base 中不存在LanguageRegistry、SyntaxHighlighter、tree-sitter feature 或组件高亮器导入且默认代码块 styled-run 测试为空从干净 diff 跑通全部聚焦测试与工作区检查。总结这份计划展示了 gpui-kit 中一次教科书式的跨 crate 架构下沉以Base-only 可用为北极星目标用测试先行的五步任务切分参考组件 → 解析/状态 → 渲染/默认样式 → 兼容门面 → Base-only 证明配合明确的依赖红线Base 永不依赖 component、高亮只走注入钩子与可执行的rg审计命令最终把富文本子系统完整地落到了 crates/base/src/text 与SelectableText组件层 crates/component/src/text/mod.rs 退化为薄门面 主题适配器 可选的 tree-sitter 高亮桥接。对使用者而言结论很简单只依赖gpui-base就能渲染、选择、复制 HTML/Markdown需要主题一致性与语法高亮时再由宿主按既有公开路径注入。【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考