gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package math
  4. import (
  5. "bytes"
  6. giteaUtil "code.gitea.io/gitea/modules/util"
  7. "github.com/yuin/goldmark/ast"
  8. "github.com/yuin/goldmark/parser"
  9. "github.com/yuin/goldmark/text"
  10. "github.com/yuin/goldmark/util"
  11. )
  12. type blockParser struct {
  13. parseDollars bool
  14. parseSquare bool
  15. endBytesDollars []byte
  16. endBytesSquare []byte
  17. }
  18. // NewBlockParser creates a new math BlockParser
  19. func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser {
  20. return &blockParser{
  21. parseDollars: parseDollars,
  22. parseSquare: parseSquare,
  23. endBytesDollars: []byte{'$', '$'},
  24. endBytesSquare: []byte{'\\', ']'},
  25. }
  26. }
  27. // Open parses the current line and returns a result of parsing.
  28. func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
  29. line, segment := reader.PeekLine()
  30. pos := pc.BlockOffset()
  31. if pos == -1 || len(line[pos:]) < 2 {
  32. return nil, parser.NoChildren
  33. }
  34. var dollars bool
  35. if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
  36. dollars = true
  37. } else if b.parseSquare && line[pos] == '\\' && line[pos+1] == '[' {
  38. if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) {
  39. // do not process escaped attention block: "> \[!NOTE\]"
  40. return nil, parser.NoChildren
  41. }
  42. dollars = false
  43. } else {
  44. return nil, parser.NoChildren
  45. }
  46. node := NewBlock(dollars, pos)
  47. // Now we need to check if the ending block is on the segment...
  48. endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesSquare)
  49. idx := bytes.Index(line[pos+2:], endBytes)
  50. if idx >= 0 {
  51. // for case: "$$ ... $$ any other text" (this case will be handled by the inline parser)
  52. for i := pos + 2 + idx + 2; i < len(line); i++ {
  53. if line[i] != ' ' && line[i] != '\n' {
  54. return nil, parser.NoChildren
  55. }
  56. }
  57. segment.Start += pos + 2
  58. segment.Stop = segment.Start + idx
  59. node.Lines().Append(segment)
  60. node.Closed = true
  61. node.Inline = true
  62. return node, parser.Close | parser.NoChildren
  63. }
  64. // for case "\[ ... ]" (no close marker on the same line)
  65. for i := pos + 2 + idx + 2; i < len(line); i++ {
  66. if line[i] != ' ' && line[i] != '\n' {
  67. return nil, parser.NoChildren
  68. }
  69. }
  70. segment.Start += pos + 2
  71. node.Lines().Append(segment)
  72. return node, parser.NoChildren
  73. }
  74. // Continue parses the current line and returns a result of parsing.
  75. func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
  76. block := node.(*Block)
  77. if block.Closed {
  78. return parser.Close
  79. }
  80. line, segment := reader.PeekLine()
  81. w, pos := util.IndentWidth(line, reader.LineOffset())
  82. if w < 4 {
  83. endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesSquare)
  84. if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) {
  85. if util.IsBlank(line[pos+len(endBytes):]) {
  86. newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)
  87. reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
  88. return parser.Close
  89. }
  90. }
  91. }
  92. start := segment.Start + giteaUtil.Iif(pos > block.Indent, block.Indent, pos)
  93. seg := text.NewSegmentPadding(start, segment.Stop, segment.Padding)
  94. node.Lines().Append(seg)
  95. return parser.Continue | parser.NoChildren
  96. }
  97. // Close will be called when the parser returns Close.
  98. func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
  99. // noop
  100. }
  101. // CanInterruptParagraph returns true if the parser can interrupt paragraphs,
  102. // otherwise false.
  103. func (b *blockParser) CanInterruptParagraph() bool {
  104. return true
  105. }
  106. // CanAcceptIndentedLine returns true if the parser can open new node when
  107. // the given line is being indented more than 3 spaces.
  108. func (b *blockParser) CanAcceptIndentedLine() bool {
  109. return false
  110. }
  111. // Trigger returns a list of characters that triggers Parse method of
  112. // this parser.
  113. // If Trigger returns a nil, Open will be called with any lines.
  114. //
  115. // We leave this as nil as our parse method is quick enough
  116. func (b *blockParser) Trigger() []byte {
  117. return nil
  118. }