code.gitea.io/gitea@v1.19.3/modules/markup/markdown/math/inline_parser.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package math 5 6 import ( 7 "bytes" 8 9 "github.com/yuin/goldmark/ast" 10 "github.com/yuin/goldmark/parser" 11 "github.com/yuin/goldmark/text" 12 ) 13 14 type inlineParser struct { 15 start []byte 16 end []byte 17 } 18 19 var defaultInlineDollarParser = &inlineParser{ 20 start: []byte{'$'}, 21 end: []byte{'$'}, 22 } 23 24 // NewInlineDollarParser returns a new inline parser 25 func NewInlineDollarParser() parser.InlineParser { 26 return defaultInlineDollarParser 27 } 28 29 var defaultInlineBracketParser = &inlineParser{ 30 start: []byte{'\\', '('}, 31 end: []byte{'\\', ')'}, 32 } 33 34 // NewInlineDollarParser returns a new inline parser 35 func NewInlineBracketParser() parser.InlineParser { 36 return defaultInlineBracketParser 37 } 38 39 // Trigger triggers this parser on $ or \ 40 func (parser *inlineParser) Trigger() []byte { 41 return parser.start[0:1] 42 } 43 44 func isAlphanumeric(b byte) bool { 45 // Github only cares about 0-9A-Za-z 46 return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') 47 } 48 49 // Parse parses the current line and returns a result of parsing. 50 func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { 51 line, _ := block.PeekLine() 52 53 if !bytes.HasPrefix(line, parser.start) { 54 // We'll catch this one on the next time round 55 return nil 56 } 57 58 precedingCharacter := block.PrecendingCharacter() 59 if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) { 60 // need to exclude things like `a$` from being considered a start 61 return nil 62 } 63 64 // move the opener marker point at the start of the text 65 opener := len(parser.start) 66 67 // Now look for an ending line 68 ender := opener 69 for { 70 pos := bytes.Index(line[ender:], parser.end) 71 if pos < 0 { 72 return nil 73 } 74 75 ender += pos 76 77 // Now we want to check the character at the end of our parser section 78 // that is ender + len(parser.end) 79 pos = ender + len(parser.end) 80 if len(line) <= pos { 81 break 82 } 83 if !isAlphanumeric(line[pos]) { 84 break 85 } 86 // move the pointer onwards 87 ender += len(parser.end) 88 } 89 90 block.Advance(opener) 91 _, pos := block.Position() 92 node := NewInline() 93 segment := pos.WithStop(pos.Start + ender - opener) 94 node.AppendChild(node, ast.NewRawTextSegment(segment)) 95 block.Advance(ender - opener + len(parser.end)) 96 97 trimBlock(node, block) 98 return node 99 } 100 101 func trimBlock(node *Inline, block text.Reader) { 102 if node.IsBlank(block.Source()) { 103 return 104 } 105 106 // trim first space and last space 107 first := node.FirstChild().(*ast.Text) 108 if !(!first.Segment.IsEmpty() && block.Source()[first.Segment.Start] == ' ') { 109 return 110 } 111 112 last := node.LastChild().(*ast.Text) 113 if !(!last.Segment.IsEmpty() && block.Source()[last.Segment.Stop-1] == ' ') { 114 return 115 } 116 117 first.Segment = first.Segment.WithStart(first.Segment.Start + 1) 118 last.Segment = last.Segment.WithStop(last.Segment.Stop - 1) 119 }