Skip to content

[Feature] Add source-position navigation and reading anchors / 增加源码位置导航与阅读锚点 API #6

Closed

Description

@dotnet9

English

Use case

A Markdown editor needs to synchronize source caret/scroll with the rendered preview and restore the reader's location after changing layout, font, theme, or the document text. This is needed for Vex's split view and detachable reading windows.

Reviewed: TKCode.StreamMark.Avalonia 0.1.0-alpha.23, Avalonia 12.1.4, .NET 10.

Existing capabilities

MarkdownView already exposes GetOutline(), ScrollToPoint(TextPoint) and ScrollToBlock(int). TextPoint identifies an entry/block/segment/plain-text offset, and model blocks have SourceRange. These are useful building blocks, but are not a public bidirectional mapping between arbitrary Markdown source offsets and rendered positions.

Sources: MarkdownView.cs, DocumentNavigation.cs, TextPoint.cs.

Requested improvement

Provide supported APIs for:

  1. navigating to a source character offset, with a documented line/column conversion convention;
  2. resolving a rendered point or selection back to a source range, returning an approximate block range when exact mapping is unavailable;
  3. capturing/restoring a reading anchor: source location plus intra-block/viewport displacement, rather than only a percentage scroll offset.

Exact mapping for every syntax need not ship first. Block-level accuracy with an explicit precision/result status would already be useful. Treat source positions as tied to a document identity/version, and document behavior before snapshots/layout are ready.

Acceptance criteria

  • A source position inside a paragraph, list or code block can navigate the preview; it need not be a heading.
  • Rendered text containing emphasis/links does not silently equate plain-text offsets with Markdown source offsets.
  • Repeated headings, CRLF and CJK/surrogate-pair text follow a documented offset convention.
  • Anchors survive width/font/theme changes within the same source, without restoring onto a different document.
  • For edited source, remapping may be supplied by the host; deleted or unavailable targets have an explicit fallback.

This request covers navigation/anchor primitives. Preserving state during a refresh is a separate concern.

中文

使用场景与已有能力

Vex 的源码/预览分栏、独立阅读窗口需要源码光标与预览位置联动,并在布局、字体或主题变化后恢复阅读位置。

目前已有大纲、ScrollToPoint 和 ScrollToBlock,模型也有源码范围;但 TextPoint 的片段纯文本偏移不能直接视为 Markdown 原文偏移,尚缺面向宿主的双向源码映射与阅读锚点 API。

建议

提供源码字符偏移跳转、渲染位置/选区到源码范围的映射,以及阅读锚点的捕获和恢复。锚点应结合源码位置与块内/视口位移,并绑定文档身份与版本,避免仅保存滚动百分比。

可以先支持块级定位,并返回精度状态,不要求第一版实现所有语法的逐字符精确映射。

验收

支持段落、列表、代码块中的非标题位置跳转;粗体和链接等语法不会混淆纯文本与原文偏移;明确 CRLF、中文、代理对和重复标题的坐标约定;同一原文改变宽度/字体/主题后可以恢复位置;跨文档不误恢复;编辑后的映射可由宿主提供,目标消失或布局未准备完成时有明确行为。

本条关注导航与锚点基础能力,刷新过程中的状态保持另行讨论。

Activity

  1. 239573049 commented on Oct 21, 2026

    @239573049
    Collaborator

    Thanks for the detailed report — implemented in #8 (released in 0.1.0-alpha.24).

    What it adds on MarkdownView (block-level precision, as you suggested):

    • ScrollToSourceOffset(offset) → SourceNavigationResult. It scrolls to the deepest block containing the offset, so a list item or a paragraph inside a quote works, not only headings; a code block is targeted as a whole. When nothing moves, the status says why: NoDocument, OutOfRange or LayoutPending.
    • GetSourceRange(TextPoint) / GetSelectionSourceRange() map a rendered point or the selection back to the source range of the covering blocks. TextPoint.Offset stays a plain-text offset inside a segment and is never treated as a source offset.
    • CaptureAnchor() / RestoreAnchor(anchor) keep the character at the top of the outer ScrollViewer plus its pixel displacement. Anchors are tied to MarkdownView.DocumentId, so they are not restored onto a different document. If the anchored block was deleted, the view goes to the next block (Approximate).
    • Offset convention (documented, tested with CRLF, CJK and surrogate pairs): UTF-16 code units, i.e. string indexes; \r\n counts as 2. SourceText.ToLocation/ToOffset convert to/from 0-based line and column.

    Limits: precision is block-level (no line/character inside a block); anchors need an ancestor ScrollViewer. The anchor test covers a width change; font and theme changes go through the same re-layout path but are not tested separately.

    The edit case is covered by RefreshMarkdown (see #5). Feedback on the API shape is welcome — this is a pre-release, so it can still change.

    感谢详细的反馈,已在 #8 中实现(已随 0.1.0-alpha.24 发布)。

    MarkdownView 新增(块级精度):

    • ScrollToSourceOffset(offset):滚动到包含该偏移的最深的块,列表项、引用里的段落都可以,不限标题;代码块按整块定位。没有滚动时用状态说明原因(NoDocument / OutOfRange / LayoutPending)。
    • GetSourceRange(TextPoint) / GetSelectionSourceRange():把渲染位置或选区反查为所覆盖块的源码区间。TextPoint.Offset 仍是片段纯文本偏移,不会被当成源码偏移。
    • CaptureAnchor() / RestoreAnchor(anchor):记录外层 ScrollViewer 可视区顶部的字符及其像素位移,绑定 MarkdownView.DocumentId,不会恢复到另一个文档;锚点所在块被删除时落到下一个块(Approximate)。
    • 偏移约定(已写入文档,并用 CRLF、中文、代理对测试):UTF-16 code unit(即 string 下标),\r\n 占 2。SourceText.ToLocation/ToOffset 做 0 起始的行列换算。

    局限:只有块级精度;锚点需要外层有 ScrollViewer。锚点测试覆盖了宽度变化,字体 / 主题变化走同一条重排路径,但没有单独测试。

    编辑后的映射由 RefreshMarkdown 处理(见 #5)。欢迎对 API 形态提意见——目前是预发布版本,仍可调整。

  2. added a commit that references this issue on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions