拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Warp 记事本 Markdown 的 Mermaid 图表渲染:从代码块识别、异步 SVG 渲染到特性开关的完整实现解析

Warp 记事本 Markdown 的 Mermaid 图表渲染:从代码块识别、异步 SVG 渲染到特性开关的完整实现解析
  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

本文围绕 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 } } } }

这里有两个值得注意的细节:

  1. 识别依赖上游 API:mermaid_to_svg::is_mermaid_diagram负责判断 info string 是否为 Mermaid 图类型(如mermaid、mermaid sequenceDiagram等),Warp 侧不复制判断逻辑;
  2. 特性开关参与分类:只有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特性开关由三部分构成,当前仓库均已就位:

  1. Cargo feature:app/Cargo.toml 中定义markdown_mermaid = []与editable_markdown_mermaid = []两个 feature(其中markdown_mermaid出现在默认启用的 feature 列表,行 652);
  2. FeatureFlag枚举:app/src/features.rs 注册FeatureFlag::MarkdownMermaid与FeatureFlag::EditableMarkdownMermaid两个条目(用#[cfg(feature = "...")]条件编译),其枚举定义与默认值逻辑位于 crates/warp_core/src/features.rs;
  3. 应用注册 + 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::Mermaidcrates/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」一节给出了清晰的工程协作节奏:

  1. 渲染形态确认后并行:外部mermaid_to_svg依赖的 Cargo 集成与特性开关管线(Cargo feature /FeatureFlag/ 注册)可以和记事本渲染路径开发并行推进——两者接口面小,解耦明显;
  2. 分类层先行:如果 markdown/代码块分类改动引入了新的规范化 Mermaid 处理(如CodeBlockType::Mermaid),应先于最终记事本接线落地;否则记事本渲染工作可以临时基于保留的围栏语言字符串 key,之后再收敛到统一分类;
  3. 合流后验证:两个工作流合并后进行完整验证——解析器/编辑器测试、记事本编辑器测试以及本仓库的全量构建检查。

当前仓库的状态表明这套顺序已被遵守:分类层(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.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

相关推荐

上一篇:Falcor渲染图系统详解:10个技巧掌握现代渲染流程设计
下一篇:在 inngest 仓库中用 Go Lambda 处理 AWS CodeDeploy 部署事件:aws-lambda-go events 包完整解读

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表