markdown

package
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: AGPL-3.0 Imports: 43 Imported by: 0

README

ui/markdown

把 Markdown 渲染成 ui/el 元素,针对 AI 聊天的流式输出优化:普通文档只重新解析正在写的块;含脚注、引用定义或宏时共享整篇解析上下文,临时补全未闭合的语法,写完的块复用元素和布局。

  • 依赖:el、core、theme、locale,以及内部的 ui/internal/imageload(图片)和经 el 间接依赖的 ui/internal/editorstyle;第三方:goldmark(解析)、golang.org/x/net/html(HTML)、chroma(代码高亮)、Gio text.Shaper(字形排版)。
  • 被谁依赖:应用代码。
文件 内容
markdown.go Doc:切块、增量解析、流式补全
plugins.go 文档级 Goldmark 扩展、块视图与行内原子控件工厂
parse.go goldmark 语法树 → 中间结构(段落、代码块、列表、表格…)
render.go 中间结构 → el 元素;富文本、代码高亮、块缓存
code_extensions.go 代码块操作槽及按语言替换展示,保留源文档
code.go 代码卡片、语言与操作图标、悬停提示、换行切换和横向滚动
math_parse.go 数学分隔符与常用 TeX 子集解析、源码回退
math_more.go 扩展 TeX:更多符号与函数、数学字母表、重音、二项式、括号、颜色、方框、更多环境
html.go 行内 HTML 标签样式、HTML 块转 Markdown 块
math_macros.go、math_structures.go 文档宏、嵌套矩阵与配对分隔符
math_delimiters.go 自动伸缩分隔符绘制
references.go 文档级解析、脚注跳转与返回
images.go 异步图片资源共享与布局缓存失效
math_layout.go 公式盒子排版、分式根号、上下标和矩阵
text.go 富文本排版、字形坐标、装饰、链接和选区绘制
stream_fade.go 增量文字及样式淡入、独立片段计时与减少动画
preview.go 整篇行高预算、完整行裁剪及截断状态
ranges.go 渲染文本快照、UTF-8 区间高亮、变更迁移和最小纵向定位
selection.go 文档坐标、跨块选区、边缘自动滚动、整篇选中和纯文本复制
selection_units.go Unicode 选词、三击选段、公式与代码行边界

使用和设计:Markdown。

原子输入引用由 el.InputDocument 接入 ui/internal/inputcontent,后者只保存文本、引用范围、选区和编辑事务,不依赖 Gio 或其他 Keel 模块。kit 和 markdown 仅经 el 间接依赖它。

Documentation

Overview

Package markdown renders Markdown as ui/el elements, built for AI chat: text that streams in a few characters at a time.

doc := markdown.New("")
doc.SetStreaming(true)
go func() {
	for tok := range tokens {
		core.Update(func() { doc.Append(tok) })
	}
	core.Update(func() { doc.SetStreaming(false) })
}()
... el.Div().Child(doc.Render(cx)) ...

Streaming stays cheap and steady:

  • The source is split into top-level chunks at blank lines outside code and display-math fences. Each chunk is parsed once and kept; an append only reparses the chunks it changed, normally just the last one.
  • While streaming, unfinished inline syntax at the end (**bold, `code, ~~strike, [link](url) is closed provisionally, so text does not flash between raw markers and formatting as tokens arrive.
  • A caret marks where text is arriving.

Supported: paragraphs, headings, emphasis, strikethrough, inline code, links, autolinks, fenced code with syntax highlighting and a copy button, block quotes, ordered, unordered and task lists, GFM tables, rules, and a native subset of TeX mathematics. Images show as their alt text.

Index

Constants

This section is empty.

Variables

View Source
var (
	CodeBg       color.NRGBA
	CodeBorder   color.NRGBA
	CodeHover    color.NRGBA
	InlineCode   color.NRGBA
	InlineCodeBg color.NRGBA
	CodeStyle    = "" // a chroma style name
	MonoFace     = theme.MonoFace
)

Colors and fonts of rendered Markdown. Change them before rendering.

View Source
var SelectionBg = color.NRGBA{R: 0xb4, G: 0xd5, B: 0xfe, A: 0xff}

SelectionBg is the color behind selected text.

Functions

func DecodeImage

func DecodeImage(ctx context.Context, source string) (image.Image, error)

DecodeImage is the default loader: local paths, file, HTTP(S) and data URLs; PNG, JPEG, GIF (first frame) and WebP; 16 MiB and 32 megapixels at most.

Types

type CodeBlockContext

type CodeBlockContext struct {
	ID, Language, Text string
	Wrapped            bool
}

CodeBlockContext is a snapshot of a fenced or indented code block. Text is the original code, without the Markdown fence. ID remains stable while the parser preserves the block's presentation state.

type Doc

type Doc struct {
	// contains filtered or unexported fields
}

Doc is a Markdown document, rendered as an el element. Change it only under the UI lock: from callbacks, or from other goroutines through core.Update.

func New

func New(src string) *Doc

New creates a document from Markdown source.

func (*Doc) Append

func (d *Doc) Append(s string)

Append adds text at the end, e.g. the next tokens of a streaming answer.

func (*Doc) CodeBlockActions

func (d *Doc) CodeBlockActions(fn func(*el.Context, CodeBlockContext) el.Element) *Doc

CodeBlockActions appends controls to the default code header. It is called each render, including for code in lists/quotes. Nil removes the extension. Handlers may change application state; rendering must not mutate the document.

func (*Doc) CodeBlockRenderer

func (d *Doc) CodeBlockRenderer(language string, fn func(*el.Context, CodeBlockContext) el.Element) *Doc

CodeBlockRenderer replaces code cards for one language (case insensitive). Return nil to retain highlighting and standard actions. An empty language targets unlabelled code. Passing nil unregisters the renderer. Custom views own their controls and text selection; the source is never executed by Doc.

func (*Doc) FrontMatter

func (d *Doc) FrontMatter() string

FrontMatter returns the YAML of the document's front matter, without the --- lines, or "" if it has none.

func (*Doc) ImageLoader

func (d *Doc) ImageLoader(loader ImageLoader) *Doc

ImageLoader sets how local or remote image sources are read. Configure it before the first Render. A nil loader uses DecodeImage.

func (*Doc) IsClamped

func (d *Doc) IsClamped() bool

IsClamped reports whether the most recently painted frame hid content because of MaxLines. It is false before the first frame and after removing the limit.

func (*Doc) MaxLines

func (d *Doc) MaxLines(n int) *Doc

MaxLines limits the document to n body-line heights, including block spacing. Zero or a negative value removes the limit. Taller headings can consume more than one line of this budget. It does not add an ellipsis or expand button.

func (*Doc) Meta

func (d *Doc) Meta() map[string]string

Meta reads the front matter's top-level "key: value" lines, with quotes around a value removed. Nested and list values are kept as written; use FrontMatter with a YAML parser for those.

func (d *Doc) OnLink(fn func(url string)) *Doc

OnLink sets what happens when a link is clicked; by default nothing.

func (*Doc) Plugins

func (d *Doc) Plugins(plugins ...Plugin) *Doc

Plugins replaces this document's plugins and reparses its source. Configure once, not every Render. Empty input restores the built-in parser. Registered documents parse as a whole so custom block syntax can cross blank lines. Factories may run again on each edit; reuse widgets/views in application code when their interactive state must survive reparsing. Render runs every frame.

func (*Doc) Render

func (d *Doc) Render(cx *el.Context) el.Element

Render draws the document. Put it in a view's tree like any element.

func (*Doc) RenderedText

func (d *Doc) RenderedText() string

RenderedText returns the selectable text from the most recently painted document. It is empty before the first frame. Block separators are included; decorations and custom code renderers are excluded. After changing source, wait for the next frame before using its offsets in the range APIs.

func (*Doc) RevealRange

func (d *Doc) RevealRange(r TextRange) bool

RevealRange requests minimum vertical scrolling to expose the line containing the range start in the nearest enclosing ScrollY. Empty ranges are allowed. True means the request was accepted, not that an ancestor can scroll. Only the latest request survives; source changes cancel it and it expires in 1s.

func (*Doc) SetRangeHighlights

func (d *Doc) SetRangeHighlights(items []RangeHighlight) bool

SetRangeHighlights replaces all highlights, returning false without changing them if any range is invalid or the source has not yet been painted. Empty input always clears them. Changes preserve highlights wholly within the unchanged prefix or suffix; highlights touching replaced text are dropped.

func (*Doc) SetSource

func (d *Doc) SetSource(src string)

SetSource replaces the document. Unchanged leading chunks keep their parse. If the source changes, the selection is cleared; Append preserves it.

func (*Doc) SetStreaming

func (d *Doc) SetStreaming(on bool)

SetStreaming marks whether text is still arriving: while it is, unfinished syntax at the end is closed provisionally and a caret shows. Set it to false when the answer is complete.

func (*Doc) ShowFrontMatter

func (d *Doc) ShowFrontMatter(on bool) *Doc

ShowFrontMatter shows the front matter as a table of its keys at the top of the document instead of hiding it.

func (*Doc) Source

func (d *Doc) Source() string

Source returns the Markdown as written.

func (*Doc) StreamFade

func (d *Doc) StreamFade(enabled bool) *Doc

StreamFade opts in to fading newly appended text over 350ms with cubic ease-out. Replacing content shows it immediately. Existing text, selection, search highlights and card controls do not fade. Reduced motion disables it.

func (*Doc) StreamFadeDuration

func (d *Doc) StreamFadeDuration(duration time.Duration) *Doc

StreamFadeDuration sets a duration from zero (instant) through ten seconds. Values outside that range are ignored. The default is 350ms.

func (*Doc) Streaming

func (d *Doc) Streaming() bool

Streaming reports whether text is still arriving.

type ImageLoader

type ImageLoader func(context.Context, string) (image.Image, error)

ImageLoader reads an image source off the UI thread.

type InlineObject

type InlineObject struct {
	Text   string
	Widget core.Widget
}

InlineObject is an indivisible inline widget. Text supplies its selectable and copied representation and must be nonempty. The widget receives the remaining line width and wraps as a whole when it does not fit. Its bottom aligns with the text baseline. Keep application state in the widget itself.

type Plugin

type Plugin struct {
	Extensions []goldmark.Extender
	Blocks     map[ast.NodeKind]func(ast.Node, []byte) el.View
	Inlines    map[ast.NodeKind]func(ast.Node, []byte) InlineObject
}

Plugin extends one document's Goldmark parser and maps AST nodes to native views. Factories run during parsing, under the UI lock; treat node/source as read-only and do not mutate Doc. A nil block view or nil inline widget falls back to the built-in conversion. Later plugins win for duplicate node kinds. HTML conversion and fenced code contents are not passed through these maps.

type RangeHighlight

type RangeHighlight struct {
	Range      TextRange
	Background color.NRGBA
}

RangeHighlight paints a background under text and beneath the selection. Later entries paint over earlier entries where their ranges overlap.

type TextRange

type TextRange struct{ Start, End int }

TextRange is a half-open UTF-8 byte range in RenderedText, not Markdown source.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL