gitea

Development moved to Codeberg

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
  119. 119
  120. 120
  121. 121
  122. 122
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package math

import (
	"bytes"

	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"
)

type blockParser struct {
	parseDollars bool
}

// NewBlockParser creates a new math BlockParser
func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
	return &blockParser{
		parseDollars: parseDollarBlocks,
	}
}

// Open parses the current line and returns a result of parsing.
func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
	line, segment := reader.PeekLine()
	pos := pc.BlockOffset()
	if pos == -1 || len(line[pos:]) < 2 {
		return nil, parser.NoChildren
	}

	dollars := false
	if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
		dollars = true
	} else if line[pos] != '\\' || line[pos+1] != '[' {
		return nil, parser.NoChildren
	}

	node := NewBlock(dollars, pos)

	// Now we need to check if the ending block is on the segment...
	endBytes := []byte{'\\', ']'}
	if dollars {
		endBytes = []byte{'$', '$'}
	}
	idx := bytes.Index(line[pos+2:], endBytes)
	if idx >= 0 {
		segment.Stop = segment.Start + idx + 2
		reader.Advance(segment.Len() - 1)
		segment.Start += 2
		node.Lines().Append(segment)
		node.Closed = true
		return node, parser.Close | parser.NoChildren
	}

	reader.Advance(segment.Len() - 1)
	segment.Start += 2
	node.Lines().Append(segment)
	return node, parser.NoChildren
}

// Continue parses the current line and returns a result of parsing.
func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
	block := node.(*Block)
	if block.Closed {
		return parser.Close
	}

	line, segment := reader.PeekLine()
	w, pos := util.IndentWidth(line, 0)
	if w < 4 {
		if block.Dollars {
			i := pos
			for ; i < len(line) && line[i] == '$'; i++ {
			}
			length := i - pos
			if length >= 2 && util.IsBlank(line[i:]) {
				reader.Advance(segment.Stop - segment.Start - segment.Padding)
				block.Closed = true
				return parser.Close
			}
		} else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) {
			reader.Advance(segment.Stop - segment.Start - segment.Padding)
			block.Closed = true
			return parser.Close
		}
	}

	pos, padding := util.IndentPosition(line, 0, block.Indent)
	seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
	node.Lines().Append(seg)
	reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
	return parser.Continue | parser.NoChildren
}

// Close will be called when the parser returns Close.
func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
	// noop
}

// CanInterruptParagraph returns true if the parser can interrupt paragraphs,
// otherwise false.
func (b *blockParser) CanInterruptParagraph() bool {
	return true
}

// CanAcceptIndentedLine returns true if the parser can open new node when
// the given line is being indented more than 3 spaces.
func (b *blockParser) CanAcceptIndentedLine() bool {
	return false
}

// Trigger returns a list of characters that triggers Parse method of
// this parser.
// If Trigger returns a nil, Open will be called with any lines.
//
// We leave this as nil as our parse method is quick enough
func (b *blockParser) Trigger() []byte {
	return nil
}