- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
本文围绕 Warp 开源仓库中 specs/APP-3077/TECH.md 这份技术设计文档展开。该文档规划了「在记事本(Notebook)的 GFM Markdown 中自动识别并渲染 Mermaid 图表」的完整技术方案,涵盖外部渲染器依赖引入、共享解析层分类、异步 SVG 资源、特性开关门控与测试策略。从仓库源码看,这套设计已经在
app/src/notebooks与crates/editor、crates/markdown_parser中落地,本文将以设计文档为主线,结合实现源码与测试用例,帮助读者理解 Warp 记事本中 Mermaid 渲染的完整链路与工程取舍。
一、背景:为什么记事本需要 Mermaid 渲染
Warp 的记事本(Notebook)支持以 Markdown 形式承载文档与代码块。在实际使用中,用户经常需要在文档中插入架构图、流程图、时序图等图示。Mermaid 是一种以纯文本描述图表、再渲染为矢量图的 DSL(领域专用语言),非常适合在 Markdown 场景下使用。
按照 specs/APP-3077/PRODUCT.md 的定义,本次功能的核心诉求是:
- 自动识别:当 Mermaid 出现在记事本内 GFM Markdown 文档中时,被自动识别并渲染;
- 双视图:Raw 视图中,Mermaid 代码块保持原样、与作者编写时完全一致;Rendered 视图中,同一代码块渲染为图表图片;
- 非阻塞:渲染可能耗时,UI 应显示加载占位,且生成工作在后台线程异步完成,绝不阻塞 UI;
- 保持可编辑:渲染后的图表不影响底层 Markdown 文本的可编辑性与 round-trip(原文导出)。
二、现状分析:既有基础设施与设计约束
specs/APP-3077/TECH.md 的「Current state」部分系统梳理了改动所依托的既有能力,这是理解方案取舍的关键。
2.1 共享的 markdown/buffer 渲染管线
记事本正文已经接入 Warp 的共享富文本管线:记事本视图通过NotebooksEditorModel::reset_with_markdown/update_to_new_markdown将 Markdown 委托给共享的富文本编辑器 reset/delta 路径,并最终由Buffer::from_markdown完成文本到缓冲区的转换。对应源码位于:
- app/src/notebooks/editor/model.rs(
reset_with_markdown等入口); - crates/editor/src/model.rs(
reset_with_markdown/update_to_new_markdown的富文本 reset/delta 实现); - crates/editor/src/content/buffer.rs(
Buffer::from_markdown的缓冲区构建)。
这意味着:Mermaid 渲染不需要另起炉灶,只要在共享管线中「插入」一个渲染分支,记事本、AI 文档等所有复用该管线的消费者都可能受益(是否启用则由作用域门控决定,见后文)。
2.2 解析器现状:代码块保留 info string
crates/markdown_parser是 Warp 的 Markdown 解析器。当前它对代码块只做两种特殊处理:warp-embedded-object(内嵌对象)与warp-markdown-table(表格);其余围栏代码块统一产出FormattedTextLine::CodeBlock,并且保留原始的语言 info string。对应实现位于 crates/markdown_parser/src/markdown_parser.rs:
pub const EMBED_BLOCK_MARKDOWN_LANG: &str = "warp-embedded-object"; pub const RUNNABLE_BLOCK_MARKDOWN_LANG: &str = "warp-runnable-command"; pub const CODE_BLOCK_DEFAULT_MARKDOWN_LANG: &str = "text"; pub const TABLE_BLOCK_MARKDOWN_LANG: &str = "warp-markdown-table";解析围栏代码块时,语言标识被映射为内嵌对象、表格或普通CodeBlockText { lang, code }(markdown_parser.rs)。随后在编辑器层,CodeBlockText会被规范化为CodeBlockType枚举(crates/editor/src/content/text.rs 的From<&CodeBlockText> for CodeBlockType实现)。「解析器保留 info string」这一现状,使 Mermaid 识别可以低成本地建立在围栏语言名之上,而无需重写解析器。
2.3 共享异步图片基础设施
Warp 的共享编辑器渲染层已经具备成熟的异步图片能力:资源(Asset)可以是Async或Raw两种形态,SVG 字节由图片缓存原生解析并渲染,相关实现位于 crates/warpui_core/src/assets/asset_cache.rs 与 crates/warpui_core/src/image_cache.rs。TECH.md 指出,Mermaid 渲染应复用这套 asset/image 缓存,使图表生成发生在 UI 线程之外,而不是再造一套图片加载机制。
2.4 关键约束:为什么不能存成普通 Markdown 图片
TECH.md 特别强调了一个容易被忽视的约束:普通图片块在富文本模型的命中测试(hit-testing)路径中不可文本编辑(见 crates/editor/src/content/core.rs 与 crates/editor/src/render/model/location.rs)。因此,若把 Mermaid 渲染结果持久化为普通 Markdown 图片,会导致记事本丢失可编辑的 Mermaid 围栏源码,破坏 round-trip。正确做法是:存储/导出保持不变,渲染作为「记事本渲染路径」的增强,而不是「Markdown 改写为图片」。
2.5 特性开关先例
Warp 的特性开关遵循既有的FeatureFlag+ Cargo feature + 应用注册模式(见 crates/warp_core/src/features.rs 与 app/Cargo.toml)。MarkdownTables(表格渲染)是离 Mermaid 最近的记事本 Markdown 先例,新功能将沿用完全相同的三件套模式。
2.6 独立 Mermaid 渲染器仓库
TECH.md 明确:Mermaid 渲染器以独立纯 Rust 仓库存在(mermaid-to-svg),对外暴露单一的render_mermaid_to_svgAPI,并自带主题支持;该仓库还内嵌dagre_rust路径依赖。Warp 应将其作为外部 Cargo 依赖消费,而不是把两个 crate 复制进本仓库,以此保持可复现性、避免重复代码,并让渲染器修复先在上游落地。
三、总体设计:TECH.md 的 Proposed changes 逐条拆解
3.1 依赖集成:外部仓库 + 固定 git revision
TECH.md 要求以固定 git revision的方式引入mermaid_to_svg,并将其(连同嵌套的dagre_rustfork)作为唯一事实来源。这一点在当前仓库 Cargo.toml 中已经落地:
# Cargo.toml (workspace 依赖) mermaid_to_svg = { git = "https://github.com/warpdotdev/mermaid-to-svg.git", rev = "8d3f789c2eb49335d7bf247a06bb649f59b6d4ed" }固定 revision 而非跟随分支,保证了构建的可复现性——这是依赖外部渲染器时的基本工程纪律。
3.2 共享层的 Mermaid 识别:分类而非重写解析器
TECH.md 要求把 Mermaid 识别放在共享的 markdown/代码块分类层,避免在业务代码里散落裸字符串比对。实现中,这个分类点正是CodeBlockText→CodeBlockType的转换(crates/editor/src/content/text.rs):
impl From<&CodeBlockText> for CodeBlockType { fn from(code_block_text: &CodeBlockText) -> Self { // Markdown blocks can contain metadata after the language, e.g.: // ```rust path=/foo start=1 // Only use the first token as the language identifier. let lang = code_block_text .lang .as_str() .split_whitespace() .next() .unwrap_or("") .to_lowercase(); if MARKDOWN_SHELL_LANGUAGES.contains(lang.as_str()) { CodeBlockType::Shell } else if FeatureFlag::MarkdownMermaid.is_enabled() && mermaid_to_svg::is_mermaid_diagram(code_block_text.lang.as_str()) { CodeBlockType::Mermaid } else { // ... 其余语言映射为 CodeBlockType::Code { lang } } } }这里有两个值得注意的细节:
- 识别依赖上游 API:
mermaid_to_svg::is_mermaid_diagram负责判断 info string 是否为 Mermaid 图类型(如mermaid、mermaid sequenceDiagram等),Warp 侧不复制判断逻辑; - 特性开关参与分类:只有
FeatureFlag::MarkdownMermaid启用时才会把代码块分类为Mermaid,从而保证「解析/分类」与「渲染」可以整体被开关门控(关闭时退化为普通代码块渲染)。
3.3 存储/导出不变,渲染作为独立路径
TECH.md 的核心原则:记事本 Markdown 存储与导出保持原样,Mermaid 渲染是渲染路径的增强。渲染路径从 Mermaid 源码派生一个异步 SVG asset,复用现有 asset/image 缓存,使图表生成脱离 UI 线程。当前实现集中在 crates/editor/src/content/mermaid_diagram.rs:
pub fn mermaid_asset_source(source: &str) -> AssetSource { let source = source.to_string(); let mut hasher = DefaultHasher::new(); source.hash(&mut hasher); let id = format!("configured:{:x}", hasher.finish()); let fetch_source = source.clone(); AssetSource::Async { id: AsyncAssetId::new::<MermaidDiagramAsset>(id), fetch: Arc::new(move || { let source = fetch_source.clone(); Box::pin(async move { mermaid_to_svg::render_mermaid_to_svg(&source, None) .map(|svg| Bytes::from(svg.into_bytes())) .map_err(Into::into) }) }), } }该实现与 TECH.md 的设计完全对应:
AssetSource::Async走异步加载,渲染在后台完成,不阻塞 UI;- asset id 由源码哈希派生(
configured:{:x}),源码变化即产生新 asset,天然支持增量失效; - 调用
render_mermaid_to_svg(&source, None)——第二个参数为None,对应 TECH.md 中「本迭代硬编码 Mermaid light 主题输出,不把终端主题变化穿透到 asset 失效」的决定。
mermaid_diagram_layout(同文件第 42-52 行)则把 asset 与布局配置(宽度、高度、间距)组合,供渲染元素使用,对应的渲染元素位于 crates/editor/src/render/element/mermaid.rs。
3.4 Notebook 作用域的渲染钩子
TECH.md 要求把渲染钩子作用域限定在记事本编辑器,通过扩展记事本渲染状态/配置实现,使其他 Markdown 消费者在显式启用前不受影响。当前仓库的实现细节:
- 编辑器模型新增
default_mermaid_display_mode: MarkdownDisplayMode字段(app/src/notebooks/editor/model.rs),默认Raw; MarkdownDisplayMode枚举定义在 app/src/notebooks/file/mod.rs(Rendered/Raw两个变体),并由 app/src/view_components/markdown_toggle_view.rs 的MarkdownToggleView(包装SegmentedControl<MarkdownDisplayMode>)提供 Rendered / Raw 切换 UI;- 渲染分支通过
EditorViewAction::MermaidDisplayModeSelected动作切换单个代码块的显示模式(app/src/notebooks/editor/notebook_command.rs 附近的icon_button实现,提供带 tooltip 的 Raw/代码图标按钮与「Rendered」按钮)。
同时,编辑器布局管线将 Mermaid 代码块分支到专门的布局任务,而不是在通用文本布局任务上携带 Mermaid 专用状态(对应 crates/editor/src/render/model/mod.rs 的布局模型与 app/src/notebooks/editor/view.rs 的记事本视图)。
3.5 Mermaid 专用 block 渲染路径
TECH.md 特别指出:Mermaid 渲染应复用记事本既有代码块模型模式(NotebookCommand),而不是复用不可编辑的普通图片块。当前仓库中,NotebookCommand承载 Mermaid 代码块(app/src/notebooks/editor/notebook_command.rs),并维护mermaid_display_mode: MarkdownDisplayMode字段;其状态判断逻辑:
pub(crate) fn is_rendered_mermaid(&self, ctx: &AppContext) -> bool { matches!(self.code_block_type(ctx), CodeBlockType::Mermaid) && matches!(self.mermaid_display_mode, MarkdownDisplayMode::Rendered) }对应 app/src/notebooks/editor/model.rs 的测试辅助方法nested_rendered_mermaid_command_count(统计渲染态 Mermaid 块数量),以及 crates/editor/src/render/element/runnable_command.rs 的既有可运行命令块渲染模式。
3.6 特性开关三件套
按 Warp 惯例,MarkdownMermaid特性开关由三部分构成,当前仓库均已就位:
- Cargo feature:app/Cargo.toml 中定义
markdown_mermaid = []与editable_markdown_mermaid = []两个 feature(其中markdown_mermaid出现在默认启用的 feature 列表,行 652); FeatureFlag枚举:app/src/features.rs 注册FeatureFlag::MarkdownMermaid与FeatureFlag::EditableMarkdownMermaid两个条目(用#[cfg(feature = "...")]条件编译),其枚举定义与默认值逻辑位于 crates/warp_core/src/features.rs;- 应用注册 + is_enabled() 门控:记事本侧通过
FeatureFlag::MarkdownMermaid.is_enabled()守卫渲染分支。
TECH.md 要求该开关同时覆盖解析/分类与渲染:开关关闭时,Mermaid 围栏按普通代码块渲染(原始文本可见);开启后才在记事本视图中渲染图表。源码中分类层(text.rs的is_enabled()检查)与渲染层(notebook_command.rs的 UI 分支检查)的两处守卫印证了这一要求。
3.7 剪贴板行为:保留纯文本,可选附加 HTML
TECH.md 明确剪贴板行为边界:选中 Mermaid 块的复制保持普通复制语义——纯文本保留原文,可附加用于 Mermaid 渲染的 HTML,但不向剪贴板放置图片字节;专门的「复制图片」能力推迟到后续迭代(考虑到跨平台与异步复杂度)。
对应测试 app/src/notebooks/editor/model_tests.rs 中的test_copy_mermaid_code_block_adds_html_without_image_clipboard_data验证了精确行为:
- 剪贴板纯文本为
```mermaid\ngraph TD\nA --> B\n```(围栏原文); - HTML 中包含
language-mermaid类与data:image/svg+xml;base64,前缀的内嵌 SVG(供支持 HTML 的粘贴目标使用); - 剪贴板图片数据(
clipboard.images)为None。
3.8 测试矩阵
TECH.md 规划的测试覆盖四类核心行为,仓库中均有对应套件:
| 测试维度 | 验证要点 | 仓库位置 |
|---|---|---|
| Mermaid 块识别 | 围栏语言被正确分类为CodeBlockType::Mermaid | crates/editor/src/content/text_tests.rs、crates/editor/src/content/mermaid_diagram_tests.rs |
| Markdown round-trip | 内部 markdown 与转义导出均与输入逐字一致 | crates/editor/src/content/markdown_tests.rs 的test_mermaid_markdown_round_trip |
| 特性开关门控 | 关闭时禁止渲染与切换 | app/src/notebooks/editor/model_tests.rs 的test_mermaid_feature_flag_disables_rendering_and_toggle |
| 异步 SVG 渲染/缓存 | asset 异步生成、缓存命中、布局尺寸 | crates/editor/src/content/mermaid_diagram_tests.rs、crates/editor/src/render/model/location_tests.rs |
编辑器层还有一个覆盖多种图类型的回归样本文件 crates/editor/test_fixtures/mermaid_sample.md,包含 flowchart 形状、样式、时序图等,用于在真实渲染路径上做端到端验证。
四、行为规格:Raw/Rendered 双视图与渲染生命周期
结合 specs/APP-3077/PRODUCT.md 的产品行为定义,可以补充 TECH.md 未展开的交互细节:
4.1 Raw 与 Rendered 视图
- Raw 视图:Mermaid 代码块保持围栏源码原样可见,用户在记事本中看到的就是编写的内容;
- Rendered 视图:同一代码块显示为渲染出的图表图片;用户可通过
MarkdownToggleView分段控件(Rendered / Raw)在两种模式间切换(app/src/view_components/markdown_toggle_view.rs)。
4.2 渲染生命周期与加载占位
Mermaid 渲染耗时,UI 需要先显示加载占位;生成工作通过AssetSource::Async在后台完成,图表生成绝不阻塞 UI。asset 缓存命中后,后续渲染直接复用已缓存的 SVG 解析结果。
4.3 滚动与尺寸策略
这是 PRODUCT.md 着墨最多的部分,也是 Mermaid 渲染体验的核心:
- 图表随外层记事本自然滚动,不脱离内容流;
- 渲染态下 Mermaid 是响应式块内容而非固定尺寸缩略图:
- 默认渲染在「自然宽度」与「适配宽度」之间取优——图表自然宽度能放进记事本内容区时按自然宽度显示;
- 超出可用宽度时等比缩小适配,不做拉伸小图填满全宽(PRODUCT.md 明确不 stretch 小图);
- 渲染高度由所选宽度下的纵横比推导,不套用固定的小默认高度(避免图在更大的盒子内被再次缩小);
- 布局必须预留渲染图片高度,保证图表完整可见、不裁切、不加黑边、不与下方内容重叠;若适配宽度后仍很高,记事本正常滚动即可。
对应实现中,mermaid_diagram_config以layout.max_width() - spacing.x_axis_offset()为宽度上限计算尺寸(crates/editor/src/content/mermaid_diagram.rs),并定义了DEFAULT_MERMAID_HEIGHT_LINE_MULTIPLIER = 10.0(按行高倍数估算加载中占位高度)与FAILED_MERMAID_HEIGHT_LINE_MULTIPLIER = 2.0(渲染失败时的占位高度)两个常量,供图片尺寸尚未就绪的过渡态使用。
4.4 主题与导出
- 主题:TECH.md 与 PRODUCT.md 一致确认——本迭代不要求 Mermaid 图表主题与终端主题匹配(
render_mermaid_to_svg(&source, None)即固定 light 主题),未来可能引入主题感知的 asset 失效; - 导出:导出 Markdown 时导出的是生成图表的原始 Markdown(围栏源码),
test_mermaid_markdown_round_trip中buffer.markdown_unescaped()与输入完全一致即是该行为的最小验证。
五、落地顺序与并行化建议(原文继承)
TECH.md 的「Parallelization」一节给出了清晰的工程协作节奏:
- 渲染形态确认后并行:外部
mermaid_to_svg依赖的 Cargo 集成与特性开关管线(Cargo feature /FeatureFlag/ 注册)可以和记事本渲染路径开发并行推进——两者接口面小,解耦明显; - 分类层先行:如果 markdown/代码块分类改动引入了新的规范化 Mermaid 处理(如
CodeBlockType::Mermaid),应先于最终记事本接线落地;否则记事本渲染工作可以临时基于保留的围栏语言字符串 key,之后再收敛到统一分类; - 合流后验证:两个工作流合并后进行完整验证——解析器/编辑器测试、记事本编辑器测试以及本仓库的全量构建检查。
当前仓库的状态表明这套顺序已被遵守:分类层(CodeBlockType::Mermaid)与渲染路径(mermaid_diagram.rs+NotebookCommand)均已存在,验证套件(markdown_tests.rs、model_tests.rs、text_tests.rs、mermaid_diagram_tests.rs)齐备。
六、从源码验证设计落地的关键路径速查
下表汇总了本文引用的核心源码位置,便于读者按图索骥深入研读:
| 关注点 | 源码位置 |
|---|---|
| 外部渲染器依赖(固定 revision) | Cargo.toml |
| 特性 Cargo feature 注册 | app/Cargo.toml(markdown_mermaid/editable_markdown_mermaid) |
FeatureFlag枚举 | app/src/features.rs、crates/warp_core/src/features.rs |
| 解析层代码块 info string 保留 | crates/markdown_parser/src/markdown_parser.rs |
CodeBlockType::Mermaid分类 | crates/editor/src/content/text.rs |
| 异步 SVG asset 与布局 | crates/editor/src/content/mermaid_diagram.rs |
| 渲染元素 | crates/editor/src/render/element/mermaid.rs |
| 记事本块模型与 Raw/Rendered 切换 | app/src/notebooks/editor/notebook_command.rs |
| 显示模式枚举与切换控件 | app/src/notebooks/file/mod.rs、app/src/view_components/markdown_toggle_view.rs |
| 回归样本与测试 | crates/editor/test_fixtures/mermaid_sample.md、crates/editor/src/content/markdown_tests.rs、app/src/notebooks/editor/model_tests.rs |
七、总结
specs/APP-3077/TECH.md 规划了一条「低成本、高复用、可回退」的 Mermaid 集成路径:不复制渲染器源码、不重写解析器、不改变存储格式,而是通过共享分类层识别 + 既有异步 asset 缓存渲染 + 记事本作用域渲染钩子 + 特性开关门控四件事完成闭环。从当前仓库源码看,这一设计已完整落地:
- 依赖以固定 git revision 引入(Cargo.toml);
- 分类层由
mermaid_to_svg::is_mermaid_diagram+FeatureFlag::MarkdownMermaid.is_enabled()双重守卫产出CodeBlockType::Mermaid(crates/editor/src/content/text.rs); - 渲染走
AssetSource::Async异步加载 SVG、源码哈希作为 asset id、light 主题硬编码(crates/editor/src/content/mermaid_diagram.rs); - 记事本侧以
NotebookCommand+MarkdownDisplayMode实现 Raw/Rendered 双视图与按块切换(app/src/notebooks/editor/notebook_command.rs); - 剪贴板保持「纯文本 + 可选 HTML、无图片字节」的克制策略,round-trip 与门控行为均有测试固化(app/src/notebooks/editor/model_tests.rs)。
这套方案最值得借鉴的工程经验在于:在处理「富文本可编辑内容中嵌入渲染产物」这类需求时,明确划分「存储层(保持原文)」与「渲染层(异步增强)」的边界,并让所有新行为服从统一的特性开关治理,从而在功能迭代与回退安全之间取得平衡。
- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
相关推荐
Warp 编辑器 Mermaid 渲染覆盖全解:test_fixtures 图表清单与 SVG 渲染管线剖析
Warp 编辑器 Mermaid 渲染覆盖全解:test_fixtures 图表清单与 SVG 渲染管线剖析 本文以 Warp 开源仓库中的 mermaid_s
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体终极指南:markdown-it表格解析从文本识别到HTML渲染的完整技术实现
终极指南:markdown it表格解析从文本识别到HTML渲染的完整技术实现 在现代文档编写中,表格是不可或缺的重要元素。 markdown it 作为一款高
开发工具CLIVoyager 的 Mermaid 图表自动渲染:从检测、安全渲染到全屏交互的实现解析
Voyager 的 Mermaid 图表自动渲染:从检测、安全渲染到全屏交互的实现解析 当 Gemini 在回答中输出 Mermaid 代码块(流程图、时序图、
AI 应用前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考