返回 DeepSeek-Reasonix
mathnode.go
根目录 / internal / cli / mathnode.go
1 package cli
2
3 import (
4 "bytes"
5
6 "github.com/yuin/goldmark/ast"
7 "github.com/yuin/goldmark/parser"
8 "github.com/yuin/goldmark/text"
9 )
10
11 var kindMath = ast.NewNodeKind("Math")
12
13 type mathNode struct {
14 ast.BaseInline
15 value string // Unicode rendering for terminal display
16 source string // original LaTeX source (e.g. "\alpha", "\frac{1}{2}")
17 display bool
18 }
19
20 func (n *mathNode) Kind() ast.NodeKind { return kindMath }
21 func (n *mathNode) Dump(src []byte, level int) { ast.DumpHelper(n, src, level, nil, nil) }
22
23 type mathParser struct{}
24
25 func (p *mathParser) Trigger() []byte { return []byte{'$'} }
26
27 func (p *mathParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
28 line, _ := block.PeekLine()
29 if len(line) == 0 || line[0] != '$' {
30 return nil
31 }
32 display := len(line) >= 2 && line[1] == '$'
33 delim := 1
34 if display {
35 delim = 2
36 }
37
38 rest := line[delim:]
39 var closeAt int
40 if display {
41 closeAt = bytes.Index(rest, []byte("$$"))
42 } else {
43 closeAt = bytes.IndexByte(rest, '$')
44 }
45 if closeAt < 0 {
46 return nil
47 }
48 inner := rest[:closeAt]
49 if len(bytes.TrimSpace(inner)) == 0 {
50 return nil
51 }
52
53 // Currency guard (markdown-it-texmath rule): a single-$ span only counts as
54 // math when the open isn't followed by space, the close isn't preceded by
55 // space, and the char after the close isn't a digit — so "$5 and $10" stays
56 // prose. Display $$ is unambiguous and skips the check.
57 if !display {
58 after := closeAt + 1
59 if inner[0] == ' ' || inner[len(inner)-1] == ' ' ||
60 (after < len(rest) && rest[after] >= '0' && rest[after] <= '9') {
61 return nil
62 }
63 }
64
65 block.Advance(delim + closeAt + delim)
66 src := string(bytes.TrimSpace(inner))
67 return &mathNode{value: latexToUnicode(src), source: src, display: display}
68 }
69
69 lines GO