| 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 |