fcode_block.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package parser
  2. import (
  3. "bytes"
  4. "github.com/yuin/goldmark/ast"
  5. "github.com/yuin/goldmark/text"
  6. "github.com/yuin/goldmark/util"
  7. )
  8. type fencedCodeBlockParser struct {
  9. }
  10. var defaultFencedCodeBlockParser = &fencedCodeBlockParser{}
  11. // NewFencedCodeBlockParser returns a new BlockParser that
  12. // parses fenced code blocks.
  13. func NewFencedCodeBlockParser() BlockParser {
  14. return defaultFencedCodeBlockParser
  15. }
  16. type fenceData struct {
  17. char byte
  18. indent int
  19. length int
  20. node ast.Node
  21. }
  22. var fencedCodeBlockInfoKey = NewContextKey()
  23. func (b *fencedCodeBlockParser) Trigger() []byte {
  24. return []byte{'~', '`'}
  25. }
  26. func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
  27. line, segment := reader.PeekLine()
  28. pos := pc.BlockOffset()
  29. if pos < 0 || (line[pos] != '`' && line[pos] != '~') {
  30. return nil, NoChildren
  31. }
  32. findent := pos
  33. fenceChar := line[pos]
  34. i := pos
  35. for ; i < len(line) && line[i] == fenceChar; i++ {
  36. }
  37. oFenceLength := i - pos
  38. if oFenceLength < 3 {
  39. return nil, NoChildren
  40. }
  41. var info *ast.Text
  42. if i < len(line)-1 {
  43. rest := line[i:]
  44. left := util.TrimLeftSpaceLength(rest)
  45. right := util.TrimRightSpaceLength(rest)
  46. if left < len(rest)-right {
  47. infoStart, infoStop := segment.Start-segment.Padding+i+left, segment.Stop-right
  48. value := rest[left : len(rest)-right]
  49. if fenceChar == '`' && bytes.IndexByte(value, '`') > -1 {
  50. return nil, NoChildren
  51. } else if infoStart != infoStop {
  52. info = ast.NewTextSegment(text.NewSegment(infoStart, infoStop))
  53. }
  54. }
  55. }
  56. node := ast.NewFencedCodeBlock(info)
  57. pc.Set(fencedCodeBlockInfoKey, &fenceData{fenceChar, findent, oFenceLength, node})
  58. return node, NoChildren
  59. }
  60. func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
  61. line, segment := reader.PeekLine()
  62. fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
  63. w, pos := util.IndentWidth(line, reader.LineOffset())
  64. if w < 4 {
  65. i := pos
  66. for ; i < len(line) && line[i] == fdata.char; i++ {
  67. }
  68. length := i - pos
  69. if length >= fdata.length && util.IsBlank(line[i:]) {
  70. newline := 1
  71. if line[len(line)-1] != '\n' {
  72. newline = 0
  73. }
  74. reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
  75. return Close
  76. }
  77. }
  78. pos, padding := util.IndentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent)
  79. if pos < 0 {
  80. pos = util.FirstNonSpacePosition(line)
  81. if pos < 0 {
  82. pos = 0
  83. }
  84. padding = 0
  85. }
  86. seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
  87. // if code block line starts with a tab, keep a tab as it is.
  88. if padding != 0 {
  89. preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
  90. }
  91. node.Lines().Append(seg)
  92. reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
  93. return Continue | NoChildren
  94. }
  95. func (b *fencedCodeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
  96. fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
  97. if fdata.node == node {
  98. pc.Set(fencedCodeBlockInfoKey, nil)
  99. }
  100. }
  101. func (b *fencedCodeBlockParser) CanInterruptParagraph() bool {
  102. return true
  103. }
  104. func (b *fencedCodeBlockParser) CanAcceptIndentedLine() bool {
  105. return false
  106. }