D0001: Rua Markdown 模块系统与语义标注设计 (Historical)

设计目标

为个人知识库(PKM)在 Markdown 之上建立一套逻辑表示层,使其具备类似编程语言模块系统的层级组织、命名引用和元信息标注能力,同时保持 Markdown 的人类可读性与生态兼容性。

核心原则:

  1. Markdown 为主:笔记的日常 authoring 仍然是 .md,不替换为 Typst 或其他格式。

  2. 逻辑路径优先:引用基于模块路径 vault::pages::intro,而非文件系统路径或标题字符串。

  3. 轻量标注:通过 Rust-like 的 #[...]{} attribute 语法为内容块附加机器可解析的元信息。

  4. LSP 原生:模块解析、跳转、补全、诊断由 Rua 核心 / LSP 提供,不依赖特定编辑器。

核心概念

概念

说明

示例

Vault

整个知识库,逻辑根模块

vault

Module

一个 .md 文件,知识库的基本单元;README 的模块路径为其父目录路径

/pages/about.mdvault::pages::about

Scope

用户显式标注的内容范围,可 inline 可 block,可跨多个 Block

#[...]{内容}

Block

按 Markdown 结构隐式切分的索引单元,如段落、章节、代码块

一个段落 / 章节

Label

Scope 的标识符,唯一且互斥,可用于引用

#intro

Tag

Scope 的分类标记,可多个

draft

Class

展示/样式类,可多个

.highlight

Attribute

键值对元信息

status=draft

模块系统

文件系统 ↔ 逻辑模块映射

规则:

  • 目录结构决定 Module 的层级关系。

  • 普通 .md 文件的模块路径为其所在目录路径加上文件名(不含 .md 后缀)。

  • README.md 是特殊的:它的模块路径是其父目录的路径,而非 README 本身的名字。

  • 因此,xxx.mdxxx/README.md 不能共存于同一父目录下,二者会映射到同一个模块路径(与 Rust 中 mod.rs 规则一致)。

  • 若某目录下的 README.md 不存在,该目录仍被视为一个空 Module 入口。

文件系统路径

逻辑路径

说明

/README.md

vault

根模块

/pages/README.md

vault::pages

README 继承父目录名 pages

/pages/about.md

vault::pages::about

普通文件,模块名为 about

/pages/docs/README.md

vault::pages::docs

README 继承父目录名 docs

/pages/docs/guide.md

vault::pages::docs::guide

普通文件,模块名为 guide

注意:/pages.md/pages/README.md 不能同时存在,因为二者都映射到 vault::pages

引用语法

采用 wiki link 扩展:

[[intro]]                          # 当前命名空间下的 Module `intro`
[[#intro]]                         # 当前 Module 内的 block `#intro`
[[pages]]                          # Module `pages`(即 `/pages/` 目录层级的 README)
[[pages::intro]]                   # 相对路径:Module `pages` 下的子模块 `intro`
[[pages#intro]]                    # Module `pages` 内的 block `#intro`
[[pages::intro#section]]           # Module `intro` 中的 block `#section`
[[super::intro]]                   # 父模块下的 Module `intro`
[[super]]                          # 父模块本身
[[vault::pages::intro]]            # 绝对路径:仓库根下的 Module `intro`
[[vault::pages::intro#section]]    # 绝对路径到 block `#section`

规则:

  • vault:: 表示从仓库根开始的绝对路径,vault 为保留根模块别名。

  • super:: 表示父模块路径,可链式使用(如 super::super::foo)。

  • # 表示引用 Module。

  • # 表示引用目标 Module 内的 block label。

  • [[intro]] 与 block label 冲突时,优先解析为 Module。

  • 同一 Module 内 label 不允许重复;重复时 LSP 报 diagnostic 错误。

不支持的扩展

  • self:::当前模块显式引用。暂不提供,因为 [[xxx]] 已可引用当前模块下的子模块,[[#xxx]] 已可引用当前模块内的 block label。

Scope 与属性标注

Inline Scope

这是普通段落,其中 #[#concept,draft,.highlight,priority=high]{关键概念} 需要被标记。

Block Scope

#[#intro,draft,.lead,status=draft]{
这是一个 block scope,
可以跨多行、包含标题、列表、代码块。
}

多个属性也可拆成多行(类似 Rust):

#[#intro]
#[draft]
#[.lead]
#[status=draft]
{
这是一个 block scope。
}

Page 级属性

文件顶部使用 #![...] 声明整个 Module 的属性:

#![#index,math,.public,title=About,status=draft]

# About

正文开始...

Scope 与 Block 的关系

  • Scope 是语义标注层概念,由用户通过 #[...]{} 显式定义。

  • Block 是索引/向量化层概念,由解析器按 Markdown 结构隐式切分(段落、章节、代码块、列表等)。

  • 两者是平行关系:一个 Scope 可以包含多个 Block,一个 Block 也可以包含多个 inline Scope。

  • 用户显式标注的 block Scope 优先作为一个独立索引单元;未标注内容按默认 Block 切分。

属性语法

属性列表写在 #[...]#![...] 中,逗号分隔、不加空格。一个 Scope 或 Module 前可连续写多个 #[...] / #![...],效果等价于合并属性列表。

attrs     ::= attr ("," attr)*
attr      ::= "#" identifier      # label(互斥,一个 scope 只能有一个)
            | identifier          # tag(可多个)
            | "." identifier      # class(可多个)
            | identifier "=" value # key=value 属性

示例 #[#intro,draft,.lead,status=draft]{...}

  • #intro:label

  • draft:tag

  • .lead:class

  • status=draft:attribute

解析边界

Scope 的 {} 匹配需满足:

  1. 忽略代码块(fenced code block)和行内代码(inline code)以及数学公式中的 {}

  2. 结束 } 建议位于行首(或仅前导空白),降低与普通文本中 } 的歧义;inline scope 的 } 紧跟内容即可。

  3. 支持嵌套 scope,解析器按括号层级计数。

LSP 集成

Rua 核心通过 LSP 提供以下能力:

  • Go to Definition[[path#label]] 跳转到对应 Module 或 block。

  • Find References:查找 Module、label 的所有引用。

  • Rename Symbol:跨文件重命名 Module、label。

  • Autocomplete:模块路径与 label 补全。

  • Diagnostics:未解析引用、重复 label、循环引用等错误。

  • Hover Info:显示逻辑路径与属性。

与现有 Markdown / Obsidian 的关系

  • Rua 的 .md 文件仍可在 Obsidian、VS Code 等编辑器中打开和编辑。

  • [[vault::pages::intro]]#[#label]{...}#![...} 等新语法在 Obsidian 中不会被识别为 Rua 语义,但也不会破坏文件读取。

  • YAML frontmatter 作为标准 Markdown/Obsidian 元数据保留,不属于 Rua 语义标注系统;Rua 的 #![...] 等标记属于正文范围。

  • Rua 提供独立的渲染/预览层来解析这些扩展。

未决问题

  1. Module 的可见性机制:暂不设计,默认全部公开。


2026-07-04 初始设计 (claude-code, kimi-for-coding)

通过讨论确定以下转向:

  1. 放弃 Typst 作为核心格式:Typst 更适合单文档排版,不是知识库模块化的核心需求。本设计退为 Markdown 超集方案,Typst 作为未来扩展方向。

  2. 核心问题调整为「文档库的逻辑表示与模块化建模」:借鉴 Rust 模块系统,为 Markdown 仓库建立逻辑模块树。

  3. 确定 scope + attribute 语法:采用 Rust-like 属性语法。#[...]{} 用于 scope 级属性,#![...] 用于 Module 级属性;属性分为 #label(唯一)、bare tag.classkey=value 四类。早期曾考虑过 []@xxx{}@xxx 等 attribute-after-scope 语法,因 page 级属性表达 awkward 而改为 Rust-like 前缀属性。

  4. 确定引用语法:无 # 引用 Page/Module,有 # 引用 block;根模块别名从 crate 改为 vault

  5. 统一 Module 与 Page 概念:所有 .md 文件都是 Module,不存在独立的 Page 概念;README 文件代表其所在文件夹层级的 Module。

  6. 确立 README 冲突规则xxx.mdxxx/README.md 不能共存,二者映射到同一模块路径,与 Rust mod.rs 规则一致。

  7. *支持 super:: 引用*:super 表示父模块,可用于相对路径引用。

  8. 区分 Label / Tag / Class / Attribute#id 为互斥 label,bare token 为聚合 tag,.class 为展示类,key=value 为属性。

  9. Scope 与 Block 平行:Scope 是用户显式标注单元,Block 是索引隐式切分单元;Scope 可跨多个 Block。

  10. *支持多个 #[...] 属性*:一个 Scope 前可连续写多个 #[...],与 Rust 属性风格一致。

  11. YAML frontmatter 独立保留:frontmatter 作为标准 Markdown/Obsidian 元数据,不属于 Rua 语义标注系统;Rua 标记属于正文范围。

  12. 关闭剩余未决问题:同一 Module 内 label 重复即报错;Module 可见性暂不设计;self:: 由无前缀引用替代。

本设计文档是后续实现 LSP、解析器和毕设论文第 4 章(详细设计)的基础。