Skip to content

feat(project): markdown.outline 按标题输出 Markdown 的文档结构树 - #41

Merged
xiaoyumuxi merged 1 commit into
mainfrom
feat/markdown-outline
Nov 14, 2026
Merged

xiaoyumuxi merged 1 commit into
mainfrom
feat/markdown-outline

Conversation

@xiaoyumuxi

@xiaoyumuxi xiaoyumuxi commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

关联

桌面端的“结构”工具窗口只显示语言服务器的文档符号。Markdown 没有语言服务器,所以打开 .md 文件时结构窗口只显示“没有语言服务器为此文件提供结构”,而预览页的浮动目录能列出标题。这个 PR 让 Core 直接给出 Markdown 的标题结构,桌面端改动在配套 PR 1lck/Lithe-IDEA#1920(固定在本 PR 的 commit b0e19d5,本 PR 合入后改为 main 上的 commit)。

改动

  • 新增命令 markdown.outline:请求 { "source": "<Markdown 文本>" },返回 { "symbols": [...] }。符号的形状与 lsp.request 对 textDocument/documentSymbol 归一化后的结果相同(name、detail、kind、tags、deprecated、range、selectionRange、containerName、children),宿主可以直接用显示语言服务器符号的同一套界面显示。
  • 解析规则:
    • 用与 markdown.render 相同的 Comrak 方言解析,所以列出的标题和预览目录一致。为此把 render 里的 Comrak 选项抽成 markdown.rs 的 dialect(),两条命令共用,渲染结果不变。
    • 列出所有 ATX 和 setext 标题,包括引用块、列表和提示块里的标题;front matter、代码块和 <h2> 这类原始 HTML 不算标题(预览目录也不列它们,因为它们没有 data-sourcepos)。
    • name 取预览显示的文字:正文、行内代码、数学原文、短代码表情;去掉图片、脚注引用和 HTML 标签;空白折叠成一个空格并去掉首尾。文字为空的标题不列出,它那一节并入前一个标题。
    • 层级:标题挂在它之前最近的、级别更低的标题下面,所以跳级(# 后直接 ###)只缩进一层。这与预览目录的规则一致。
    • kind 固定为 15(SymbolKind.String,编辑器标记 Markdown 标题的惯例);detail、containerName 为 null,tags 为空,deprecated 为 false。
    • 位置是零起始行和 UTF-16 列。\n、\r\n 和单独的 \r 都算换行,与 Comrak 一致。selectionRange 覆盖标题本身,setext 标题包括下划线那一行;range 是整节,从标题所在行行首到下一个同级或更高级标题所在行行首,没有这样的标题就到文末。
  • 实现在新文件 rust/crates/project/src/markdown_outline.rs。lithe-project 新增对 lithe-text 的依赖,用来复用 LspRangeResponse/LspPositionResponse;lithe-text 只依赖 lithe-protocol,不形成环。
  • 命令登记:protocol/command.rs(枚举和命令名),runtime/dispatcher.rs(分发,坏请求返回 invalid_request),runtime/request_trace.rs(与 markdown.render 一样列为高频命令,不逐次追踪)。
  • 契约 rust/contracts/rust-core-api.md:命令表新增一行,“Markdown rendering”下新增“Markdown heading outline”一节,写明请求、返回和上述规则。
  • 共享样例 rust/fixtures/editor/markdown-outline-v1.json,6 个用例:ATX 与 setext 混排、front matter 保持行号、引用块里的中文和表情标题(UTF-16 列)、CRLF、跳级与代码块/原始 HTML/空标题、空文档。桌面端测试读同一份样例。

验证

  • cargo test -p lithe-project:81 个测试通过,其中 markdown_outline 有 7 个(样例、跳级与整节范围、容器里的标题、空标题、名称取字、空文档、单独 \r)。
  • cargo test -p lithe-core:820 个测试全部通过,含新增的 markdown_outline_command_answers_the_shared_fixture 和 markdown_outline_command_rejects_a_payload_without_source。
  • cargo fmt --all、cargo clippy -p lithe-project -p lithe-core --all-targets -- -D warnings、scripts/rust-core/verify-rust-core-comments.sh:通过。

不涉及插件契约、能力版本或插件函数协议,所以不需要更新协议记录。

用与 markdown.render 相同的 Comrak 方言解析,把标题按级别组织成与 LSP documentSymbol 归一化结果同形的符号树:selectionRange 是标题本身,range 是到下一个同级或更高级标题的整节;位置为零起始行和 UTF-16 列。宿主在没有语言服务器时也能为 Markdown 文件显示结构,且与预览页的目录一致。共享样例 rust/fixtures/editor/markdown-outline-v1.json。
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 11:44
@xiaoyumuxi
xiaoyumuxi merged commit 1dbc244 into main Nov 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant