code_block.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package parser
  2. import (
  3. "github.com/yuin/goldmark/ast"
  4. "github.com/yuin/goldmark/text"
  5. "github.com/yuin/goldmark/util"
  6. )
  7. type codeBlockParser struct {
  8. }
  9. // CodeBlockParser is a BlockParser implementation that parses indented code blocks.
  10. var defaultCodeBlockParser = &codeBlockParser{}
  11. // NewCodeBlockParser returns a new BlockParser that
  12. // parses code blocks.
  13. func NewCodeBlockParser() BlockParser {
  14. return defaultCodeBlockParser
  15. }
  16. func (b *codeBlockParser) Trigger() []byte {
  17. return nil
  18. }
  19. func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
  20. line, segment := reader.PeekLine()
  21. pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
  22. if pos < 0 || util.IsBlank(line) {
  23. return nil, NoChildren
  24. }
  25. node := ast.NewCodeBlock()
  26. reader.AdvanceAndSetPadding(pos, padding)
  27. _, segment = reader.PeekLine()
  28. // if code block line starts with a tab, keep a tab as it is.
  29. if segment.Padding != 0 {
  30. preserveLeadingTabInCodeBlock(&segment, reader, 0)
  31. }
  32. node.Lines().Append(segment)
  33. reader.Advance(segment.Len() - 1)
  34. return node, NoChildren
  35. }
  36. func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
  37. line, segment := reader.PeekLine()
  38. if util.IsBlank(line) {
  39. node.Lines().Append(segment.TrimLeftSpaceWidth(4, reader.Source()))
  40. return Continue | NoChildren
  41. }
  42. pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
  43. if pos < 0 {
  44. return Close
  45. }
  46. reader.AdvanceAndSetPadding(pos, padding)
  47. _, segment = reader.PeekLine()
  48. // if code block line starts with a tab, keep a tab as it is.
  49. if segment.Padding != 0 {
  50. preserveLeadingTabInCodeBlock(&segment, reader, 0)
  51. }
  52. node.Lines().Append(segment)
  53. reader.Advance(segment.Len() - 1)
  54. return Continue | NoChildren
  55. }
  56. func (b *codeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
  57. // trim trailing blank lines
  58. lines := node.Lines()
  59. length := lines.Len() - 1
  60. source := reader.Source()
  61. for length >= 0 {
  62. line := lines.At(length)
  63. if util.IsBlank(line.Value(source)) {
  64. length--
  65. } else {
  66. break
  67. }
  68. }
  69. lines.SetSliced(0, length+1)
  70. }
  71. func (b *codeBlockParser) CanInterruptParagraph() bool {
  72. return false
  73. }
  74. func (b *codeBlockParser) CanAcceptIndentedLine() bool {
  75. return true
  76. }
  77. func preserveLeadingTabInCodeBlock(segment *text.Segment, reader text.Reader, indent int) {
  78. offsetWithPadding := reader.LineOffset() + indent
  79. sl, ss := reader.Position()
  80. reader.SetPosition(sl, text.NewSegment(ss.Start-1, ss.Stop))
  81. if offsetWithPadding == reader.LineOffset() {
  82. segment.Padding = 0
  83. segment.Start--
  84. }
  85. reader.SetPosition(sl, ss)
  86. }