Files
RawMark/internal/vault/metadata_math.go
T

105 lines
3.4 KiB
Go

package vault
import (
"bytes"
"unicode"
"unicode/utf8"
"github.com/yuin/goldmark/v2/ast"
"github.com/yuin/goldmark/v2/extension"
"github.com/yuin/goldmark/v2/parser"
"github.com/yuin/goldmark/v2/text"
"github.com/yuin/goldmark/v2/util"
)
// Relationship extraction only: math and code are not body text, so #1 and example links must not become knowledge relationships.
// Delimiter rules match frontend Typora extensions; this parser does not render Markdown.
type metadataMath struct{}
func (metadataMath) Trigger() []byte { return []byte{'$'} }
func (metadataMath) Open(parent ast.Node, reader text.Reader, _ parser.Context) (ast.Node, parser.State) {
line, _ := reader.PeekLine()
if parent.Kind() != ast.KindDocument || !bytes.Equal(bytes.TrimSpace(line), []byte("$$")) {
return nil, parser.NoChildren
}
return ast.NewCodeBlock(ast.CodeBlockKindFenced, text.Lines{}), parser.NoChildren
}
func (metadataMath) Continue(node ast.Node, reader text.Reader, _ parser.Context) parser.State {
line, segment := reader.PeekLine()
reader.AdvanceToEOL()
if bytes.Equal(bytes.TrimSpace(line), []byte("$$")) {
return parser.Close
}
node.(*ast.CodeBlock).Value.AppendSegment(segment)
return parser.Continue | parser.NoChildren
}
func (metadataMath) Close(ast.Node, text.Reader, parser.Context) {}
func (metadataMath) CanInterruptParagraph() bool { return true }
func (metadataMath) CanAcceptIndentedLine() bool { return false }
func (metadataMath) Parse(_ ast.Node, reader text.Reader, _ parser.Context) ast.Node {
line, first := reader.PeekLine()
if len(line) < 2 || line[1] == '$' {
return nil
}
next, _ := utf8.DecodeRune(line[1:])
if unicode.IsSpace(next) {
return nil
}
savedLine, savedPosition := reader.Position()
reader.Advance(1)
for {
line, segment := reader.PeekLine()
if line == nil {
reader.SetPosition(savedLine, savedPosition)
return nil
}
for i := 0; i < len(line); i++ {
if line[i] == '\\' {
i++
continue
}
if line[i] != '$' || i == 0 {
continue
}
prev, _ := utf8.DecodeLastRune(line[:i])
if unicode.IsSpace(prev) {
continue
}
if i+1 < len(line) && line[i+1] >= '0' && line[i+1] <= '9' {
continue
}
reader.Advance(i + 1)
return ast.NewCodeSpan(text.NewSingleLineValueFromIndex(text.NewIndex(first.Start, segment.Start+i+1), nil))
}
reader.AdvanceLine()
}
}
func parseMetadata(source []byte) ast.Node {
math := metadataMath{}
return parser.New(parser.WithExtensions(extension.GFMParser, extension.FootnoteParser), parser.WithBlockParsers(util.Prioritized[parser.BlockParser](math, 650)), parser.WithInlineParsers(util.Prioritized[parser.InlineParser](math, 150))).Parse(source)
}
// Return non-body byte ranges so lossless link rewriting can skip example syntax inside math.
func nonProseRanges(root ast.Node) [][2]int {
var ranges [][2]int
_ = ast.Walk(root, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch node := node.(type) {
case *ast.CodeSpan:
for _, index := range node.Value.Indices() {
ranges = append(ranges, [2]int{index.Start, index.Stop})
}
return ast.WalkSkipChildren, nil
case *ast.CodeBlock:
for _, index := range node.Value.Segments() {
ranges = append(ranges, [2]int{index.Start, index.Stop})
}
return ast.WalkSkipChildren, nil
}
return ast.WalkContinue, nil
})
return ranges
}